ServiceLoader 使用 ClassLoader 指向不同的路径

ServiceLoader using ClassLoader pointing to different path

这几天一直在尝试,但无法正常工作!

我正在尝试构建一个可插入的 java 应用程序,我可以从命令行 运行 它并在单独的文件夹中提供插件(jars)。 ServiceLoader 似乎符合我的要求,但我认为我需要一个特殊情况,其中罐子不属于 class 路径,而它们存储在不同的位置,因此我需要使用 ClassLoder 将其 url 指向此文件系统路径。

我想提供给主应用程序的插件之一是带有一些自定义功能的日志 jar。

下面是我正在使用的代码,但无法进入 for/loop ..这意味着 ServiceLoader 无法 identify/match 任何 class 实施:

final URL u = new File("C:\data\myLogJar-1.0-SNAPSHOT.jar").toURI().toURL();
ClassLoader ucl = new URLClassLoader(new URL[] {u});

ServiceLoader<Log> loader = ServiceLoader.load(Log.class, ucl);
for (Iterator<Log> iterator = loader.iterator(); iterator.hasNext(); ) {
    System.out.println(iterator.next());
}
loader = ServiceLoader.load(Log.class,ucl);
for (final Log log : loader) {
    log.info("Test log");                    
}

希望你能帮上忙! 非常感谢

==== 添加项目文件:

主要可插入应用程序:

    package com.company.dep.automation;

import com.company.dep.automation.pluggable.Log;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import java.util.*;

public class Main {

    private static ServiceLoader<Log> serviceLoader;

    public static void main(String[] args) {

        final URL u;
        ClassLoader ucl = null;

        try {
            u = new File("C:\data\myLogJar-1.0-SNAPSHOT.jar").toURI().toURL();
             ucl = new URLClassLoader(new URL[]{u});
        } catch (MalformedURLException e1) {
            e1.printStackTrace();
        }

        ServiceLoader<Log> loader = ServiceLoader.load(Log.class, ucl);
        for (Iterator<Log> iterator = loader.iterator(); iterator.hasNext(); ) {
            System.out.println(iterator.next());
        }

        loader = ServiceLoader.load(Log.class, ucl);
        for (final Log log : loader) {
            log.info("Test log");
        }

    }

}

"Log" 插件

界面Log

package com.company.automation.service;

public interface Log {

    void trace(String message);
    void debug(String message);
    void info(String message);
    void warn(String message);
    void severe(String message);
    void error(String message);
    void fatal(String message);

}

它的实现

package com.company.automation.service.impl;

import com.company.automation.service.Log;

public class LogImpl implements Log {

    @Override
    public void trace(String message) {
        log("TRACE --> " + message);
    }

    @Override
    public void debug(String message) {
        log("DEBUG --> " + message);
    }

    @Override
    public void info(String message) {
        log("INFO --> " + message);
    }

    @Override
    public void warn(String message) {
        log("WARN --> " + message);
    }

    @Override
    public void severe(String message) {
        log("SEVERE --> " + message);
    }

    @Override
    public void error(String message) {
        log("ERROR --> " + message);
    }

    @Override
    public void fatal(String message) {
        log("FATAL --> " + message);
    }

    private void log(String message) {
        System.out.println(message);
    }

}

=================

项目结构调整如下,但还是不行:

主要应用程序:

扩展应用:

它不起作用,因为它不相同 class Log,您的主要方法尝试查找 com.company.dep.automation.pluggable.Log 的实现,而您的 jar 定义了 [=12] 的实现=] 这样 ServiceLoader.load 根本找不到任何东西。

您应该使用 Main class 将接口 com.company.automation.service.Log 从扩展 jar 移动到项目中,并在您的 com.company.automation.service.Log 中导入 com.company.automation.service.Log 而不是 com.company.dep.automation.pluggable.Log Main class 那么一切都应该有效。

关于应用程序的 "pluggable" 部分和文件夹中 jar 的加载,与其重新发明轮子,不如看看 OSGI。通过使用 OSGI 实现(例如 Apache Felix),您可以将 jars 插件加载到您的应用程序中。例如,这就是 Eclipse 插件的工作方式。 (Atlassian 也在他们的 Jira/Confluence/etc 插件中使用了这种 OSGI 机制)。

如果你想要一个可插入的插件系统,OSGI 将处理你有一天会遇到的很多问题。

例如,我是这样做的:

public ChannelListener init() {
    private ChannelListener listener = new ChannelListener();
    logger.info("Initializing Felix...");
    felix = new Felix(getConfig());

    try {
        felix.start();
    } catch (BundleException e) {
        logger.error("Error while initializing Felix", e);
        throw new RuntimeException(e);
    }

    try {
        // On the next line, you are registering a listener that will listen for all 
        //Log implementations service registration.
        felix.getBundleContext().addServiceListener(listener, "(objectClass=com.company.automation.service.Log)");
    } catch (InvalidSyntaxException e) {
        logger.error("Error while registering service listener", e);
    }
    listener.start(felix.getBundleContext());

    startAllBundlesInDirectory(BUNDLE_DIRECTORY, felix.getBundleContext());
    return listener;
}

private void startAllBundlesInDirectory(String directory, BundleContext context) {
    File bundleFolder = new File(directory);
    File[] bundleFilesList = bundleFolder.listFiles((dir, name) -> name.endsWith(".jar"));

    List<Bundle> installedBundles = new ArrayList<>();
    logger.info("Installing {} bundles in {}.", bundleFilesList.length, directory);
    for(File bundleFile : bundleFilesList) {
        logger.info("Installing {}", bundleFile.getName());
        try {
            installedBundles.add(context.installBundle("file:" + directory + bundleFile.getName()));
        } catch (BundleException e) {
            logger.error("Error while installing bundle {}{}", directory, bundleFile.getName(), e);
        }
    }

    for(Bundle bundle : installedBundles) {
        try {
            bundle.start();
        } catch (BundleException e) {
            logger.error("Error while starting bundle {}{}", directory, bundle.getSymbolicName(), e);
        }
    }
}