MrP*_*low 5 java spring classloader spring-boot
在这样的模块结构中:
项目
|
|- 通用模块
|- 应用模块
在 app 模块具有公共模块作为依赖项的情况下,我在公共模块中定义了一个自定义类加载器类。app 模块有一个-Djava.system.class.loader=org.project.common.CustomClassLoaderjvm 参数集来使用公共模块中定义的自定义类加载器。
在 IDEA 中运行一个 spring boot 项目,这非常有效。找到自定义类加载器,设置为系统类加载器,一切正常。
编译一个可运行的 jar(使用没有任何自定义属性的默认 spring-boot-maven-plugin),jar 本身包含所有类,并且在它的 lib 目录中是具有自定义类加载器的公共 jar。但是运行 jar 的-Djava.system.class.loader=org.project.common.CustomClassLoader结果是以下异常
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)
Run Code Online (Sandbox Code Playgroud)
为什么会发生这种情况?是因为在可运行的 jar 中,类加载器类位于 lib 目录中的 jar 中,因此类加载器试图在将 lib 类添加到类路径之前进行设置?除了将类加载器从 common 移动到所有其他需要它的模块之外,我还能做些什么吗?
编辑:我尝试将自定义类加载器类从通用模块移动到应用程序,但我仍然遇到相同的错误。这里发生了什么?
在 IDEA 中运行 Spring Boot 项目效果非常好。找到自定义类加载器,将其设置为系统类加载器,一切正常。
因为 IDEA 将您的模块放在类路径上,其中之一包含自定义类加载器。
是否因为在可运行的 jar 中,类加载器类位于 lib 目录中的 jar 中,因此类加载器试图在将 lib 类添加到类路径之前进行设置?
有点儿。lib 类不会“添加到类路径中”,但可运行的 Spring Boot 应用程序自己的自定义类加载器知道在哪里查找以及如何加载它们。
要更深入地了解java.system.class.loader,请阅读 Javadoc ClassLoader.getSystemClassLoader()(稍微重新格式化并添加枚举):
- 如果首次调用此方法时定义了系统属性
java.system.class.loader,则该属性的值将被视为将作为系统类加载器返回的类的名称。- 该类是使用默认系统类加载器加载的,并且必须定义一个公共构造函数,该构造函数
ClassLoader采用用作委托父级的单个类型参数。- 然后使用此构造函数以默认系统类加载器作为参数创建一个实例。
- 生成的类加载器被定义为系统类加载器。
- 在构造过程中,类加载器应该非常小心,避免调用
getSystemClassLoader(). 如果检测到系统类加载器的循环初始化,则会IllegalStateException抛出异常。
这里的决定性因素是#3:用户定义的系统类加载器由默认系统类加载器加载。后者当然不知道如何从嵌套 JAR 中加载某些内容。只有稍后,在 JVM 完全初始化并且 Spring Boot 的特殊应用程序类加载器启动后,才能读取这些嵌套的 JAR。
也就是说,您在这里遇到了先有鸡还是先有蛋的问题:为了在 JVM 初始化期间找到自定义类加载器,您需要使用尚未初始化的 Spring Boot 可运行 JAR 类加载器。
如果您想了解上面描述的 Javadoc 在实践中是如何完成的,请查看ClassLoader.initSystemClassLoader().
除了将类加载器从公共模块移至所有其他需要它的模块之外,我还能做些什么吗?
如果您坚持使用可运行的 JAR,即使这样也无济于事。您可以执行以下任一操作:
Thread.setContextClassLoader(),而不是尝试将其用作系统类加载器(如果这是一个可行的选择)。2020-10-28 更新:在文档“可执行 Jar 格式”中,我在“可执行 Jar 限制”下找到了以下内容:
系统类加载器:启动的应用程序应在加载类时使用
Thread.getContextClassLoader()(大多数库和框架默认这样做)。尝试加载嵌套 jar 类ClassLoader.getSystemClassLoader()失败。java.util.Logging始终使用系统类加载器。因此,您应该考虑不同的日志记录实现。
这证实了我上面写的内容,特别是我关于使用线程上下文类加载器的最后一个要点。
| 归档时间: |
|
| 查看次数: |
969 次 |
| 最近记录: |