如何将Java应用程序模块化

3
我正在尝试使我的Java应用程序模块化,这样就会有一个核心基础模块,客户端需要获取该模块,但是他/她将能够添加其他功能/插件,就像IDE(如NetBeans)一样。
我计划在客户端PC上设置一个名为modules/plugins的子目录,其中包括任何插件应用作为.jar文件。当用户启动应用程序时,主模块将包含这些其他插件,因此,例如,Stage将在同一scene中同时具有来自主模块和插件模块的按钮。
我熟悉使用视图的elgg。借鉴这个,例如在主模块上可以有一个HBox,在启动应用程序时从不同插件中填充按钮。
我该如何让core_base_module.jar调用下面plugins目录中的所有插件呢?换句话说,我该如何让一个.jar文件调用另一个.jar文件?
示例目录结构:
main dir/---core_base_module.jar
        /---plugins ---/chat.jar
                       /videoplayer.jar
                       /like.jar
                       /unlike.jar
                       /etc.jar
                       /etc_etc.jar

1
了解JVM的构建自动化工具,例如GradleMaven - mikołak
@mikotak 这不是编译时设置。问题是关于在运行时加载新模块的。 - RealSkeptic
1
好的,我想你的core_module将不得不扫描插件目录以查找插件。当你发现jar时,你应该知道你想要加载什么(一些从另一个类继承的类,这是你合同的一部分,即你应该知道你期望什么)。类加载应该像这样完成:https://dev59.com/xGgu5IYBdhLWcg3w3qt7 - darijan
1
请看这个答案:https://dev59.com/onVC5IYBdhLWcg3wvT_g - darijan
请查看这个实用工具 https://github.com/decebals/pf4j - Daniel Jipa
我很好奇为什么没有人建议使用OSGI? - Saky
2个回答

4
您可以通过以下方式手动完成此操作:
  1. 查找插件文件夹
  2. 扫描文件夹以获取所有JAR的列表
  3. 创建类加载器,连接JAR文件,然后在每个JAR中查找实现您的插件API接口的实现。
  4. 创建实例,并在您的插件注册表中注册它
虽然您可以自己完成所有这些工作,但使用Service Loader API可能更容易。该API是Java的一部分,并为此目的而创建。这就是Spring用于加载可以处理Spring XML配置文件中不同XML标记的插件的方法。例如,请查看spring-web.jar!/META-INF/services 然后,您将希望您的启动脚本像这样将所有JAR添加到主类路径中:
java -cp plugins/* -jar app.jar

如果您没有启动脚本,也可以使用Java来创建URLClassLoader,将所有想要的jar文件传递给它,然后在这个新的类加载器中调用您的主类。
更多信息:

1
如果您想创建专业级的应用程序,建议使用@DavidRoussel提出的ServiceLoader API。
如果您喜欢更轻量级的东西,并且可以手动完成,则可以使用仅包含您在应用程序中包含的抽象类或接口的API包,以及包含实际类的实现包,这些类将作为单独的jar文件提供。由于您无法通过“new”创建仅由接口知道的对象,因此您将不得不使用工厂模式和反射。您还应该使用Class.forName(“package.to.class”)来在程序开始时测试实现的存在性。
例如,假设接口Foo是可选实现的,由构造函数采用字符串参数FooImpl,则工厂可能是:
public class FooFactory {
    public static final String CLASSNAME = "...FooImpl";
    public static Foo createFoo(String key) {
        try {
            Class<?> fooclazz = Class.forName(CLASSNAME);
            Foo foo = (Foo) fooclazz.getConstructor(String.class).newInstance(key);
            return foo;
        }
        catch (Exception e) {
            // eventually process some exceptions
        }
        return null;
    }
}

您最初使用以下方式设置全局布尔值hasFoo(例如主类的静态成员):

    try {
        Class.forName(FooFactory.CLASSNAME);
        hasOpt = true;
    }
    catch (Exception e) {
        hasOpt = false;
    }

而且您可以选择使用它:

    if (MainClass.hasOpt) {
        Foo foo = FooFactory.createFoo("bar");
        // do something with foo
    }
    else {
        // warning message : Option not present
    }

使用这种方法,只需将实现jar包添加到类路径中并重新启动应用程序即可使用它。

但是你怎么知道要加载哪个类?插件的名称是什么? - David Roussel
这只在相对简单的用例中才有意义,其中所有插件都已被主应用程序知道。因此,它只尝试通过完全限定名称加载FooFactory 必须包含在主应用程序中。 - Serge Ballesta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接