【发布时间】:2021-09-23 14:57:53
【问题描述】:
我做了一些研究,但由于这种情况的复杂性,不适合我。
Child first class loader and Service Provider Interface (SPI)
像 flink 或 tomcat 一样,我的应用程序作为带有平台和系统类加载器的框架运行。 框架加载插件作为模块和插件可能依赖于一些库,因此请定义:
plugin/plugin-demo.jar
depend/plugin-demo/depend-1.jar
depend/plugin-demo/depend-2.jar
框架会像这样创建两个类加载器:
URLClassLoader dependClassloader = new URLClassLoader({URI-TO-depend-jars}, currentThreadClassLoader);
URLClassLoader pluginClassloader = new URLClassLoader({URI-TO-plugin-jar},dependClassloader);
使用 HelloWorld 演示,这是工作文件(起初我没有将 systemClassloader 设置为父级)。
但是使用 SPI 的 JDBC 驱动程序com.mysql.cj.jdbc.Driver 会遇到麻烦:
即使我手动注册驱动程序:
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver", true, pluginClassloader);
com.mysql.cj.jdbc.Driver driver = (com.mysql.cj.jdbc.Driver) clazz.getConstructor().newInstance();
DriverManager.registerDriver(driver);
这工作正常,但之后:
DriverManager.getConnection(this.hostName, this.userName, this.password)
会上升
Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:440)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 7 more
或者:
Caused by: java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/furryblack
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
我尝试打印所有驱动程序:
Enumeration<java.sql.Driver> driverEnumeration = DriverManager.getDrivers();
while (driverEnumeration.hasMoreElements()) {
java.sql.Driver driver = driverEnumeration.nextElement();
System.out.println(driver);
}
并且没有注册驱动程序。
那么,问题是:为什么是 NoClassDefFoundError ?
我有一些猜测:DriverManager 在 systemclassloader 中运行,但我的类加载器父级中的驱动程序加载不会在子级中搜索,所以我将 currentThreadClassLoader 设置为父级但仍然出现异常。
更新 1:
URI-TO-depend-jars 是 File.toURI().toURL() 的数组。 这个设计与demo配合得很好,所以我认为它应该是正确的。
通过调试,ClassLoader 父链为ModuleLoader -> DependLoader
而系统类加载器是ModuleLoader -> DependLoader -> BuiltinAppClassLoader -> PlatformClassLoader -> JDKInternalLoader
这是完整的代码:
jar 1 中的接口:
public interface AbstractComponent {
void handle();
}
jar2 中的插件(依赖 pom.xml 中的 jar3):
public class Component implements AbstractComponent {
@Override
public void handle() {
System.out.println("This is component handle");
SpecialDepend.tool();
}
}
依赖于jar3:
public class SpecialDepend {
public static void tool() {
System.out.println("This is tool");
}
}
jar1 中的主要内容:
@Test
public void test() {
String path = "D:\\Server\\Classloader";
File libFile = Paths.get(path, "lib", "lib.jar").toFile();
File modFile = Paths.get(path, "mod", "mod.jar").toFile();
URLClassLoader libLoader;
try {
URL url;
url = libFile.toURI().toURL();
URL[] urls = {url};
libLoader = new URLClassLoader(urls);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
URLClassLoader modLoader;
try {
URL url;
url = modFile.toURI().toURL();
URL[] urls = {url};
modLoader = new URLClassLoader(urls, libLoader);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
try {
Class<?> clazz = Class.forName("demo.Component", true, modLoader);
if (AbstractComponent.class.isAssignableFrom(clazz)) {
AbstractComponent instance = (AbstractComponent) clazz.getConstructor().newInstance();
instance.handle();
}
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
}
输出是
This is component handle
This is tool
这是完美的工作。
更新 2:
我尝试打印更多调试和一些不必要的代码,然后我发现,可以找到驱动程序类并实例化,但是DriverManager.registerDriver没有注册它。
所以问题变成了:为什么 DriverManager 不能从子类加载器注册驱动加载?
更新3
contextClassLoader 是从 Thread.currentThread().getContextClassLoader() 获取的,但是通过框架注入 currentThread.setContextClassLoader(exclusiveClassLoader);
作为仔细检查,我打印了哈希码,它是一样的。
我调试到 DriverManager,它已将驱动程序注册到内部列表中,但之后,getDrivers 将一无所获。
【问题讨论】:
-
{URI-TO-PLUGIN-JAR}的实际值是多少?这可能会有所帮助stackoverflow.com/a/738422/15273968 -
在更新 2 上,您能看到哪个类加载器正在加载 DriverManager?而这个
contextClassLoader引用是当前线程中java.lang.Thread#getContextClassLoader()的产物吗? -
@SaleemKhair 我用updated3更新,我发现了一些奇怪的东西,据我所知,从另一个类加载器加载的类即使关闭了URLClassLoader,仍然可以工作。
-
一个类加载器首先在它的父类中寻找类,然后父类委托给它的父类,依此类推,所以作为兄弟的类加载器不能看到彼此的类,
DriverManager#getDrivers()也在内部验证是否调用者类加载器可以使用DriverManager#isDriverAllowed(Driver, ClassLoader)加载类,因此即使在注册中填充了驱动程序,它仍然可能没有注册,这就是你得到一个空列表的原因。 -
解决了我的问题,我调试到 DriverManager,getDriver 将通过 getCaller 和调用者的 Classloader 进行检查。我需要对类加载器继承树做一些工作,但这会偏离主题,感谢这个提示!考虑写一个答案? @SaleemKhair
标签: java classloader urlclassloader