I did some research, But due to complexity of this situation, Not working for me.
Child first class loader and Service Provider Interface (SPI)
Like flink or tomcat, My application run as framework with platform and system classloader. Framework load plugin as module and plugin may depend some lib, so make this define:
plugin/plugin-demo.jar
depend/plugin-demo/depend-1.jar
depend/plugin-demo/depend-2.jar
framework will create two classloader like this:
URLClassLoader dependClassloader = new URLClassLoader({URI-TO-depend-jars}, currentThreadClassLoader);
URLClassLoader pluginClassloader = new URLClassLoader({URI-TO-plugin-jar},dependClassloader);
With an HelloWorld demo this is working file ( and at first I NOT set systemClassloader as parent).
But with JDBC driver com.mysql.cj.jdbc.Driver
which using SPI goes into trouble:
Even I manual register 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);
This working fine, But after that:
DriverManager.getConnection(this.hostName, this.userName, this.password)
will rise
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
Or:
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)
I try to print all driver:
Enumeration<java.sql.Driver> driverEnumeration = DriverManager.getDrivers();
while (driverEnumeration.hasMoreElements()) {
java.sql.Driver driver = driverEnumeration.nextElement();
System.out.println(driver);
}
And there is no driver registered.
So, Question is: why NoClassDefFoundError ?
I have some guess: DriverManager run in systemclassloader but driver load in my classloader parent won't search in children, So I set currentThreadClassLoader as parent but still rise exception.
URI-TO-depend-jars is Array of File.toURI().toURL(). This design working fine with demo, So I think it should be correct.
And with debug, The ClassLoader parent chain is
ModuleLoader -> DependLoader
And with systemclassloader is
ModuleLoader -> DependLoader -> BuiltinAppClassLoader -> PlatformClassLoader -> JDKInternalLoader
This is the full code:
Interface in jar 1:
public interface AbstractComponent {
void handle();
}
Plugin in jar2 (depend jar3 in pom.xml):
public class Component implements AbstractComponent {
@Override
public void handle() {
System.out.println("This is component handle");
SpecialDepend.tool();
}
}
Depend in jar3:
public class SpecialDepend {
public static void tool() {
System.out.println("This is tool");
}
}
Main in 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);
}
}
Output is
This is component handle
This is tool
This is working perfect.
I try to print more debug and some unnecessary code, Then I found, The Driver class can be found and instancelized, But the DriverManager.registerDriver
didn't register it.
So the question become: Why DriverManager can't register driver load from sub classloader?
contextClassLoader is get from Thread.currentThread().getContextClassLoader()
But inject by framework with currentThread.setContextClassLoader(exclusiveClassLoader);
As double check I print the hashcode, Its same.
And I debug into DriverManager, Its was registered the driver into internal List but after that, getDrivers will got nothing.
ClassLoader
looks for classes in its parent first, and the parent delegates to its parent and so on. With that said, ClassLoaders that are siblings cannot see eachothers classes.
Also the method DriverManager#getDrivers()
internally validates if the caller ClassLoader can load the class with DriverManager#isDriverAllowed(Driver, ClassLoader)
.
this means that even if your Driver
is added to the registration list, it is only added as an instance of DriverInfo
, this means that it would only be loaded on demand (Lazy), and still might not register when loading is attempted, that's why you get an empty list.