在这样的模块结构中:
项目
|
|- 公用模块
|- 应用程序模块
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,但还是出现同样的错误,这是怎么回事?
4条答案
按热度按时间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,即使这样也无济于事。你可以做的是:
Thread.setContextClassLoader()
等设置您的自定义类加载器,而不是试图将其用作系统类加载器,如果这是一个可行的选择。**更新2020-10-28:**在文档“可执行的Jar格式”中,我在“可执行的Jar限制”下找到了这个:
系统类加载器:启动的应用程序在加载类时应该使用
Thread.getContextClassLoader()
(大多数库和框架默认情况下都是这样做的)。尝试使用ClassLoader.getSystemClassLoader()
加载嵌套的jar类会失败。java.util.Logging
总是使用系统类加载器。因此,您应该考虑不同的日志实现。这证实了我上面写的内容,特别是我最后一个关于使用线程上下文类加载器的要点。
k5ifujac2#
假设你想用Spring将自定义jar添加到类路径中,请执行以下操作:
1.用maven jar插件生成jar文件
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**
vc9ivgsu3#
以下列方式提出的申请─
理想地是足够的。
将需要一些关于如何使用应用程序的清晰度。主类本身是否试图从自定义类加载器启动(假设有可能这样做),或者启动后特定的应用程序相关类是否需要使用自定义类加载器(和相关特权)加载?
已经在上面的评论中提出了这些问题(计划在这里更新答案,一旦有更多的清晰度)。
PS:还没有真正考虑到“模块”的使用,但相信上述语法仍然适用于较新的JDK(在JDK 8之后)。
v8wbuo2f4#
对于Sping Boot 应用程序,使用**-Dloader.path**在classpath上添加不同的文件夹,如下面的示例所示: