【问题标题】:How to customize where ServiceLoader looks for Plugin Jars如何自定义 ServiceLoader 查找插件罐的位置
【发布时间】:2017-05-22 03:42:22
【问题描述】:

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

如何自定义 ServiceLoader 以从核心查找和调用 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);
    }

}

View.java

public interface View {

    String getName();

    Node getView();
}

场景

我正在开发的应用程序是一个多模块独立桌面应用程序。例如,Core 将在左侧 (left-pane) 上保留一个窗格。左窗格将接受来自任何实现名为LeftPane 的接口的模块的nodesA 实现了LeftPane 接口。每当 Core 启动时,它应该扫描一个文件夹,在这种情况下是插件,并自动启动那里的所有包,包括 A,它将继续填充左窗格。

【问题讨论】:

    标签: java serviceloader


    【解决方案1】:

    当然,最简单的方法是将插件放在类路径中。然后你可以通过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 实现了Iterable,因此您可以遍历所有实现:

    for (ITextPropertyLoader textProperty : loader) {
        final String textPropertyBaseName = textProperty.getTextPropertyBaseName();
        System.out.println("Found text property with name: " + textPropertyBaseName);
    }
    

    还可以查看Oracles documentationquestion

    【讨论】:

    • @Program-Me-Rev 添加了更多关于ServiceLoader 的使用细节。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多