Java 如何自定义ServiceLoader查找插件JAR的位置

Java 如何自定义ServiceLoader查找插件JAR的位置,java,serviceloader,Java,Serviceloader,我有一个包含两个项目的多模块项目:Core和a。这个想法是在内核启动时启动/运行内核 我如何定制ServiceLoader,以便从core中查找并调用Plugins文件夹中的模块 plugin-Project + Core + src\main\java Core.java + A + src\main\java A.java + Plugins 核心 public class Core

我有一个包含两个项目的多模块项目:Corea。这个想法是在内核启动时启动/运行内核

我如何定制ServiceLoader,以便从core中查找并调用Plugins文件夹中的模块

plugin-Project
    + Core
        + src\main\java
            Core.java

    + A
        + src\main\java
            A.java

    + Plugins
核心

public class Core extends Application {

    private ServiceLoader<View> views;
    private BorderPane mainBorderPane;

    @Override
    public void init() {
        loadViews();
    }

    private void loadViews() {
        views = ServiceLoader.load(View.class);
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("Ui Application");

        mainBorderPane = new BorderPane();
        mainBorderPane.setTop(createMenuBar());

        Scene scene = new Scene(new Group(), 800, 605);
        scene.setRoot(mainBorderPane);

        stage.setScene(scene);
        stage.show();
    }

    private MenuBar createMenuBar() {
        MenuBar menuBar = new MenuBar();
        Menu viewMenu = new Menu("Views");
        menuBar.getMenus().add(viewMenu);

        ToggleGroup toggleGroup = new ToggleGroup();

        views.forEach(v -> {
            RadioMenuItem item = new RadioMenuItem(v.getName());
            item.setToggleGroup(toggleGroup);
            item.setOnAction(event -> {
                Label label = new Label(v.getName());
                mainBorderPane.setLeft(label);
                mainBorderPane.setCenter(v.getView());
            });
            viewMenu.getItems().add(item);
        });
        return menuBar;
    }

    public static void main(String[] args) {
        launch(args);
    }

}

场景


我正在使用的应用程序是一个多模块的独立桌面应用程序。例如,Core将在左侧(左窗格)上放置一个窗格。左窗格将从实现名为
LeftPane
的接口的任何模块中接受
节点A实现
LeftPane
界面。无论何时启动Core,它都应该扫描一个文件夹,即本例中的插件,并自动启动那里的所有捆绑包,包括一个,然后填充左窗格。

当然,最简单的方法是将插件放在类路径上。然后,您只需通过
ServiceLoader
访问接口即可

或者提供一种机制,在特定位置检测插件jar文件并将其添加到类路径。这是棘手的部分。一种方法是为您的应用程序使用定制的
ClassLoader
,它允许向类路径添加jar文件

我选择了一种不同的方法,即访问我的应用程序中使用的
ClassLoader
的非公共API:

private void addFile(File f) throws IOException // URL to your plugin jar file
{
    addURL(f.toURI().toURL());
}

private void addURL(URL u) throws IOException
{
    URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Class sysclass = URLClassLoader.class;

    try {
        Method method = sysclass.getDeclaredMethod("addURL", parameters);
        method.setAccessible(true);
        method.invoke(sysloader, new Object[] {u});
    } catch (Throwable t) {
        t.printStackTrace();
    }

}
当插件jar文件位于类路径上时,您可以通过
ServiceLoader
访问公开的接口。让我用一个例子来说明这一点。公开的接口可以如下所示:

/**
 * Interface to allow plugins to contribute resource reference properties.
 */
public interface IResourcePropertyLoader {
    /**
     * Retrieve the base name for the additional text resource.
     * @return
     */
    String getResourcePropertyBaseName();
}
接口(也可以是基类)是核心应用程序的一部分。插件有一个实现此接口的类

接下来,查找该接口的所有实现:

ServiceLoader<ITextPropertyLoader> loader = ServiceLoader.load(ITextPropertyLoader.class, ClassLoader.getSystemClassLoader());

还可以看看这个和这个

最简单的方法当然是让插件已经在类路径上。然后,您只需通过
ServiceLoader
访问接口即可

或者提供一种机制,在特定位置检测插件jar文件并将其添加到类路径。这是棘手的部分。一种方法是为您的应用程序使用定制的
ClassLoader
,它允许向类路径添加jar文件

我选择了一种不同的方法,即访问我的应用程序中使用的
ClassLoader
的非公共API:

private void addFile(File f) throws IOException // URL to your plugin jar file
{
    addURL(f.toURI().toURL());
}

private void addURL(URL u) throws IOException
{
    URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Class sysclass = URLClassLoader.class;

    try {
        Method method = sysclass.getDeclaredMethod("addURL", parameters);
        method.setAccessible(true);
        method.invoke(sysloader, new Object[] {u});
    } catch (Throwable t) {
        t.printStackTrace();
    }

}
当插件jar文件位于类路径上时,您可以通过
ServiceLoader
访问公开的接口。让我用一个例子来说明这一点。公开的接口可以如下所示:

/**
 * Interface to allow plugins to contribute resource reference properties.
 */
public interface IResourcePropertyLoader {
    /**
     * Retrieve the base name for the additional text resource.
     * @return
     */
    String getResourcePropertyBaseName();
}
接口(也可以是基类)是核心应用程序的一部分。插件有一个实现此接口的类

接下来,查找该接口的所有实现:

ServiceLoader<ITextPropertyLoader> loader = ServiceLoader.load(ITextPropertyLoader.class, ClassLoader.getSystemClassLoader());

还可以看看这个以及这个

@Program Me Rev添加了一些关于使用
ServiceLoader
@Program Me Rev的详细信息,添加了一些关于使用
ServiceLoader
的详细信息。