spring Sping Boot runnable jar无法找到通过java.system.class.loader jvm参数设置的classloader

ubbxdtey  于 2023-04-19  发布在  Spring
关注(0)|答案(4)|浏览(140)

在这样的模块结构中:
项目
|
|- 公用模块
|- 应用程序模块
app模块有一个common模块作为依赖项,我在common模块中定义了一个自定义的classloader类。app模块有一个-Djava.system.class.loader=org.project.common.CustomClassLoader jvm参数集,用于使用common模块中定义的自定义classloader。
在IDEA中运行一个Sping Boot 项目,这工作得很好。找到自定义类加载器,设置为系统类加载器,一切正常。
编译一个可运行的jar(使用默认的spring-boot-maven-plugin,不带任何自定义属性),jar本身具有所有的类,并且在它的lib目录中是具有自定义类加载器的公共jar。

java.lang.Error: org.project.common.CustomClassLoader
    at java.lang.ClassLoader.initSystemClassLoader(java.base@12.0.2/ClassLoader.java:1989)
    at java.lang.System.initPhase3(java.base@12.0.2/System.java:2132)
Caused by: java.lang.ClassNotFoundException: org.project.common.CustomClassLoader
    at jdk.internal.loader.BuiltinClassLoader.loadClass(java.base@12.0.2/BuiltinClassLoader.java:583)
    at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(java.base@12.0.2/ClassLoaders.java:178)
    at java.lang.ClassLoader.loadClass(java.base@12.0.2/ClassLoader.java:521)
    at java.lang.Class.forName0(java.base@12.0.2/Native Method)
    at java.lang.Class.forName(java.base@12.0.2/Class.java:415)
    at java.lang.ClassLoader.initSystemClassLoader(java.base@12.0.2/ClassLoader.java:1975)
    at java.lang.System.initPhase3(java.base@12.0.2/System.java:2132)

为什么会发生这种情况?是不是因为在可运行的jar中,classloader类位于lib目录下的jar中,所以classloader试图在lib类被添加到classpath之前进行设置?除了将classloader从common移动到所有其他需要它的模块之外,我还能做什么?

EDIT:我尝试将自定义类加载器类从common module移到app,但还是出现同样的错误,这是怎么回事?

tquggr8v

tquggr8v1#

在IDEA中运行一个Sping Boot 项目,这工作得很好。找到自定义类加载器,设置为系统类加载器,一切正常。
因为IDEA把你的模块放在类路径上,其中一个包含自定义类加载器。
是因为在可运行的jar中,类加载器类位于lib目录的jar中,所以类加载器试图在lib类被添加到类路径之前进行设置吗?
有点。lib类不是“添加到类路径”,但可运行的Sping Boot 应用程序自己的自定义类加载器知道在哪里找到以及如何加载它们。
要更深入地了解java.system.class.loader,请阅读ClassLoader.getSystemClassLoader()的Javadoc(稍微重新格式化,添加了枚举):
1.如果系统属性java.system.class.loader是在第一次调用此方法时定义的,则该属性的值将被视为将作为系统类加载器返回的类的名称。
1.该类是使用默认的系统类加载器加载的,并且必须定义一个公共构造函数,该构造函数接受一个ClassLoader类型的参数,该参数用作委托父类。
1.然后使用此构造函数***创建一个示例,并将默认系统类加载器***作为参数。
1.生成的类装入器被定义为系统类装入器。
1.在构造过程中,类加载器应该非常小心地避免调用getSystemClassLoader()。如果检测到系统类加载器的循环初始化,则抛出IllegalStateException
这里的决定性因素是#3:用户定义的系统类加载器由默认的系统类加载器加载。后者当然不知道如何从嵌套的JAR加载东西。只有在JVM完全初始化并且Sping Boot 的特殊应用程序类加载器启动之后,才能读取这些嵌套的JAR。
这是一个鸡与蛋的问题:为了在JVM初始化期间找到自定义类加载器,您需要使用尚未初始化的Sping Boot 可运行JAR类加载器。
如果你想知道上面描述的Javadoc在实践中是如何完成的,可以看看ClassLoader.initSystemClassLoader()的OpenJDK源代码。
除了将类加载器从common移到所有其他需要它的模块之外,我还能做些什么呢?
如果你坚持使用可运行的JAR,即使这样也无济于事。你可以做的是:

  • 运行您的应用程序,而不是将其压缩到可运行的JAR中,而是作为一个普通的Java应用程序,在类路径上包含所有应用程序模块(特别是包含自定义类加载器的模块)。
  • 将自定义类装入器提取到可运行JAR之外的一个单独模块中,并在运行可运行JAR时将其放在类路径上。
  • 通过Thread.setContextClassLoader()等设置您的自定义类加载器,而不是试图将其用作系统类加载器,如果这是一个可行的选择。
    **更新2020-10-28:**在文档“可执行的Jar格式”中,我在“可执行的Jar限制”下找到了这个:

系统类加载器:启动的应用程序在加载类时应该使用Thread.getContextClassLoader()(大多数库和框架默认情况下都是这样做的)。尝试使用ClassLoader.getSystemClassLoader()加载嵌套的jar类会失败。java.util.Logging总是使用系统类加载器。因此,您应该考虑不同的日志实现。
这证实了我上面写的内容,特别是我最后一个关于使用线程上下文类加载器的要点。

k5ifujac

k5ifujac2#

假设你想用Spring将自定义jar添加到类路径中,请执行以下操作:
1.用maven jar插件生成jar文件

<plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-jar-plugin</artifactId>
     <configuration>
         <archive>
             <manifest>
                 <addClasspath>true</addClasspath>
                 <classpathPrefix>libs/</classpathPrefix>
                 <mainClass>
                     com.demo.DemoApplication
                 </mainClass>
             </manifest>
         </archive>
     </configuration>
 </plugin>

1.从命令行运行应用程序时,请使用以下命令
java -cp target/demo-0.0.1-SNAPSHOT.jar -Dloader.path=<Path to the Custom Jar file> org.springframework.boot.loader.PropertiesLauncher
这应该会在加载自定义类加载器的同时启动应用
简而言之,诀窍是,使用**-Dloader.path沿着org.springframework. Boot .loader.PropertiesLauncher**

vc9ivgsu

vc9ivgsu3#

以下列方式提出的申请─

java -cp ./lib/* com.example.Main

理想地是足够的。
将需要一些关于如何使用应用程序的清晰度。主类本身是否试图从自定义类加载器启动(假设有可能这样做),或者启动后特定的应用程序相关类是否需要使用自定义类加载器(和相关特权)加载?
已经在上面的评论中提出了这些问题(计划在这里更新答案,一旦有更多的清晰度)。
PS:还没有真正考虑到“模块”的使用,但相信上述语法仍然适用于较新的JDK(在JDK 8之后)。

v8wbuo2f

v8wbuo2f4#

对于Sping Boot 应用程序,使用**-Dloader.path**在classpath上添加不同的文件夹,如下面的示例所示:

java -cp app.jar -Dloader.path=/opt/lib/ org.springframework.boot.loader.PropertiesLauncher

相关问题