【问题标题】:ServiceLoader using ClassLoader pointing to different pathServiceLoader 使用 ClassLoader 指向不同的路径
【发布时间】:2016-10-27 12:18:13
【问题描述】:

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

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

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

下面是我正在使用的代码,但无法进入 for/loop .. 这意味着 ServiceLoader 无法识别/匹配任何类实现:

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

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);
    }

}
  • 结构

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

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

主应用程序:

扩展应用程序:

【问题讨论】:

  • 请显示你的jar文件的结构
  • 对不起!添加更多信息...
  • 如果您至少有一个结果,因为您没有调用 next(),您的第一个循环将永远循环
  • 是的 .. 我做了很多尝试,可能会留下错误 - 第二个块无论如何都不会循环.. 感谢您指出它

标签: java urlclassloader serviceloader pluggable


【解决方案1】:

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

您应该使用 Main 类将接口 com.company.automation.service.Log 从扩展 jar 移动到项目中,并在 Main 类中导入 com.company.automation.service.Log 而不是 com.company.dep.automation.pluggable.Log,然后一切都应该正常工作。

【讨论】:

  • 我刚刚调整了两个项目的路径,还是不行。见我上面的“编辑”。谢谢尼古拉斯!
  • 从您的扩展应用中删除com.company.automation.pluggable.Log,请显示您的文件内容META-INF/services/com.company.automation.pluggable.Log
  • 从扩展项目中删除文件 com.company.automation.pluggable.Log 会导致编译问题,因为 LogImpl 类实现了 Log。他是服务文件内容:com.company.automation.pluggable.impl.LogImpl
  • move 登录一个新项目并使两个项目都依赖于新项目,或者只是让您的扩展应用程序依赖于主应用程序(最丑陋和最简单的解决方案)。这个想法是为了防止在类路径中有两次类 Log,否则你会得到与冲突相关的奇怪错误。确实请记住,即使从 2 个不同的 CL 加载的 2 个完全相同的类也被认为是不同的。
  • 您的建议在第一次尝试时就奏效了。您建议创建三个单一项目确实是对的:主执行器、插件扩展,当然还有作为后两者之间桥梁的 Api。谢谢!
【解决方案2】:

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

如果你想要一个可插拔的插件系统,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);
        }
    }
}

【讨论】:

  • 它看起来很有趣,需要玩一点。唯一的问题是现在只有一个 jar,但它们应该更多,所以 jar 文件名应该来自一个列表文件夹中的 jar .. 需要了解您的代码并尝试调整我的代码 ..
  • 基本上File[] bundleFilesList = bundleFolder.listFiles((dir, name) -&gt; name.endsWith(".jar"));这一行是在读取文件夹的所有jar文件。
猜你喜欢
  • 2023-03-20
  • 2015-11-25
  • 2016-05-23
  • 1970-01-01
  • 1970-01-01
  • 2019-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多