如何在不重新启动 JVM 的情况下将外部 JAR 添加到 Spring 应用程序?

How to add external JARs to Spring application without restarting JVM?

我有一个 Spring 引导应用程序,它根据特定条件将外部 JAR 文件复制到文件夹中。这些 JAR 可以包含许多 Spring 组件(即 类 用 @Component 注释或元注释)并且 Spring 应用程序应该能够扫描和实例化这些 bean。是否可以根据特定条件动态 加载 JAR 文件的内容并使它们可用于 Spring 应用程序上下文? 我完全了解这带来的安全隐患。

我已经阅读了 Spring 为其 executable JAR format, such as JarLauncher and PropertiesLauncher 提供的不同类型的 Launcher,但看起来这些启动器没有检测到类路径的更改,但是而是只扫描一次 JAR 文件的目录。

下面的简单应用程序演示了这个问题:

// .../Application.java
@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    System.out.println("Please copy JAR files and press Enter ...");
    System.in.read();
    SpringApplication.run(Application.class, args);
  }
}

将默认的 JarLauncher 替换为 PropertiesLauncher:

// build.gradle
tasks.named('bootJar') {
  manifest {
    attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher',
      'Start-Class': 'com.example.customlauncher.Application'
  }
}

PropertiesLauncher:

的属性文件中指定外部 JAR 的位置
# .../resources/loader.properties
loader.path=file:/path/to/dir

应用程序是 Spring Initializer Gradle application 并由 运行 bootJar 任务打包:./gradlew bootJar.

然后使用以下命令启动它:

java -jar build/libs/customlauncher-0.0.1-SNAPSHOT.jar

如果 JAR 文件已存在于指定位置 (/path/to/dir),则此方法有效,但如果在目录为空且 JAR 文件为空时执行 java 命令,则此方法无效然后在应用程序等待用户复制文件并按 Enter .

时复制

有几个相关的问题,但看起来他们都假定 JAR 文件在启动 JVM 时已经存在:

有没有一种方法可以在不使用太多笨拙的 hack 的情况下实现这一目标?或者建议使用 OSGi 之类的东西?我是不是完全看错了,有更好的方法让类路径上的 JAR 不需要总是需要加载(如果 JAR 被“禁用”,它不应该被 JVM loaded/compiled,不应该被 Spring 等捡起)?

如果在启动 Spring 应用程序之前复制 JAR 文件,这似乎是可能的。感觉很骇人听闻,但确实有效。 使用风险自负!

您需要两个 class,一个用于引导外部 JAR,然后通过手动创建的 PropertiesLauncher 启动第二个。引导 class 可以是普通的旧常规 Java class(但它也可以是 Spring 引导应用程序)并且只需要第二个 class一个 SpringBootApplication.

// BootstrapApplication.java
public class BootstrapApplication {
  public static void main(String[] args) {
    System.out.println("Please copy JAR files and press Enter ...");
    System.in.read();

    PropertiesLauncher.main(args);
  }
}
// Application.java
@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

在 gradle 文件中,我们可以切换回默认 JarLauncher,方法是删除 bootJar 任务清单配置并通过 springBoot 配置块应用设置. mainClass 将在 MANIFEST.MF 文件中以 Start-Class 结束。

// build.gradle
springBoot {
    mainClass = 'com.example.customlauncher.BootstrapApplication'
}

在加载程序的属性文件中,需要设置一个新的属性,它指向真实的应用程序class。此文件中的设置仅由 PropertiesLauncher 选取并被 JarLauncher 忽略。换句话说:JarLauncher 从清单文件中委托给 Start-ClassPropertiesLauncher 从其属性文件中委托给 loader.main

# .../resources/loader.properties
loader.path=file:/path/to/dir
loader.main=com.example.customlauncher.Application

Spring(启动)将首先调用 BootstrapApplication 的主要方法,如 MANIFEST.MF 文件中指定(通过 springBoot 配置块控制 build.gradle 文件)。在此 main 方法的实现中,创建了一个新的 PropertiesLauncher,并将 main class 设置为“真实”应用程序(即 Application)。

应用程序的执行仍然是通过相同的调用完成的:

java -jar build/libs/customlauncher-0.0.1-SNAPSHOT.jar

在 JVM 启动后添加到 /path/to/dir,但在 之前 在 BootstrapApplication 中调用 PropertiesLauncher#main 的任何 JAR 文件然后在 class从 Application.

中看到的路径和应用程序上下文