【问题标题】:How to make a Java app modular如何使 Java 应用程序模块化
【发布时间】:2015-08-05 15:23:21
【问题描述】:

我正在尝试使我的 Java 应用程序模块化,以便客户必须获得核心基础模块,但他/她将能够在它们出现时添加其他功能/插件,或者随着她的需求扩大。就像 NetBeans 之类的 IDE 一样。

我计划在客户端 pc 中有一个名为 modules/plugins 的子目录,其中任何插件应用程序都将包含为 .jar 文件。当用户启动应用程序时,主模块将在最后一项中包含这些其他插件,因此,例如,Stage 将在同一 scene 上同时拥有来自主模块和插件模块的按钮。

我精通elgg,它使用视图来执行此操作。借用这个,我可以有一个 HBox,例如在主模块上,它在启动应用程序时填充了来自不同插件的按钮。

我怎样才能让下面的 core_base_module.jar 调用下面插件目录中的所有插件。换句话说,我怎样才能让一个.jar 文件调用另一个.jar 文件?

示例目录结构:

main dir/---core_base_module.jar
        /---plugins ---/chat.jar
                       /videoplayer.jar
                       /like.jar
                       /unlike.jar
                       /etc.jar
                       /etc_etc.jar

【问题讨论】:

  • 查看用于 JVM 的构建自动化工具,例如 GradleMaven
  • @mikotak 这不是编译时设置。问题是关于在运行时加载新模块。
  • 好吧,我猜你的 core_module 将不得不扫描插件目录中的插件。当您发现 jar 时,您应该知道要加载的内容(从属于您的合同的另一个类扩展而来的某种类,即您应该知道您期望什么)。类加载应该是这样的:stackoverflow.com/questions/11016092/…
  • 也看看这个答案:stackoverflow.com/questions/194698/…
  • 看看这个实用程序也github.com/decebals/pf4j

标签: java spring javafx


【解决方案1】:

您可以通过以下方式手动执行此操作:

  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

或者如果你没有 lauch 脚本,你可以在 java 中做同样的事情,但是创建一个 URLClassLoader 传递你想要的所有 jar 文件,然后在这个新的类加载器中调用你的主类。

更多信息:

【讨论】:

    【解决方案2】:

    如果你真的想创建一个专业级的应用程序,@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
        }
    

    使用这种方法,在classpath中添加实现jar,重启应用即可使用。

    【讨论】:

    • 但是你怎么知道要加载哪个类呢?插件的名称是什么?
    • 这只有在一个相当简单的用例中才有意义,其中所有插件都已被主应用程序知道。所以它只是尝试通过完全限定名称加载 classesFooFactory 必须包含在主应用程序中
    猜你喜欢
    • 2020-09-01
    • 2019-08-20
    • 2013-08-15
    • 2012-10-12
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 2013-03-04
    • 1970-01-01
    相关资源
    最近更新 更多