Class.forName访问不同类加载器加载类的解决方法(Scala sbt场景)
可行解决方案
以下方案均不需要修改App2代码,仅调整App1的配置或代码即可解决问题:
方案1(优先推荐):将Bar类所在路径追加到bootstrap类路径
直接满足让bootstrap类加载器加载Bar类的需求,无需修改业务代码,适配性最高:
- 如果是本地通过
sbt run启动App1,直接在App1的build.sbt中添加如下配置:
// 将编译输出的classes目录追加到bootstrap类路径 run / javaOptions += s"-Xbootclasspath/a:${(Compile / classDirectory).value.getAbsolutePath}" // 必须开启fork,sbt才会启动独立JVM进程运行应用,使上述JVM参数生效 fork := true
- 如果是打包后独立部署,启动App1时在JVM启动参数中添加:
-Xbootclasspath/a:<App1的jar包绝对路径或编译输出classes目录绝对路径>即可。
方案2:手动注入Bar类字节码到bootstrap类加载器缓存
适合仅需少量类被App2加载、不想修改JVM启动参数的场景,在App1启动后、调用App2的foo方法前执行如下代码:
import java.nio.file.{Paths, Files} // 替换为你实际的Bar类全类名 val barFullClassName = "com.yourpackage.Bar" val barClassResourcePath = barFullClassName.replace('.', '/') + ".class" // 读取Bar类的字节码 val barClassBytes = Files.readAllBytes( Paths.get(classOf[Bar].getProtectionDomain.getCodeSource.getLocation.toURI) .resolve(barClassResourcePath) ) // 获取bootstrap类加载器 val bootstrapClassLoader = ClassLoader.getSystemClassLoader.getParent // 反射调用类加载器的defineClass方法,将Bar类注册到bootstrap加载器缓存 val defineClassMethod = classOf[ClassLoader].getDeclaredMethod( "defineClass", classOf[String], classOf[Array[Byte]], classOf[Int], classOf[Int] ) defineClassMethod.setAccessible(true) defineClassMethod.invoke(bootstrapClassLoader, barFullClassName, barClassBytes, 0, barClassBytes.length)
方案3:临时修改线程上下文类加载器(验证生效后使用)
部分JVM环境下Class.forName会优先读取当前线程的上下文类加载器,可尝试在调用foo方法时临时替换上下文类加载器:
val originalClassLoader = Thread.currentThread.getContextClassLoader try { // 将上下文类加载器替换为加载Bar类的类加载器 Thread.currentThread.setContextClassLoader(classOf[Bar].getClassLoader) // 调用App2的foo方法 xxx.foo("com.yourpackage.Bar") } finally { // 恢复原类加载器,避免影响其他业务逻辑 Thread.currentThread.setContextClassLoader(originalClassLoader) }
内容的提问来源于stack exchange,提问作者gollyzoom
相关产品推荐
相关产品推荐

