本文将通过一个示例代码说明URLClassLoader加载Class时的几个问题:
- Class.forName(…)方法确实是使用传递的classLoader加载目标类
- initialize参数传递false不会初始化静态资源
- 使用classLoader加载的类,使用了其余的类,jvm仍然会使用classLoader加载这个类
遇到的问题
之前在做maven插件(feign接口自动生成工具)的时候,需要扫描maven工程类,但是mvn在运行时又不会加载工程类,好在maven插件中可以通过project将工程类和依赖jar获取到,所以可以自己编写代码把这些类和jar加载进来。
在java1.8版本,可以先获取到当前系统类加载器(ClassLoader.getSystemClassLoader()方式获取),此ClassLoader是一个URLClassLoader实例,所以可以通过反射调用addURL方法将工程类和依赖jar加载到当前jvm中,大概的方式是这样的:
File file = new File(jar);
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
URL url1 = new URL("file:" + file.getAbsolutePath());
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
method.invoke(classLoader, url1);
但是之后使用java11版本时,发现系统类加载器不再是URLClassLoader实例,强制转型时就失败了,而且反射调用非public方法也不再允许,所以只能另外寻找解决方案。于是想到了在程序里面创建一个URLClassLoader实例,通过这个URLClassLoader加载工程类和依赖jar,大概思路是这样的:
- 使用URLClassLoader加载类库
- 使用Class.forName(“className”, initialize, classLoader)方法将具体类加载到jvm。注意这里的initialize要为false,如果传递true,会对静态代码进行初始化,可能出错。classLoader就是我们前面使用的URLClassLoader
下面我们使用一个示例程序证明一下以下几个问题:
- Class.forName(…)方法确实是使用传递的classLoader加载目标类
- initialize参数传递false不会初始化静态资源
- 使用classLoader加载的类,使用了其余的类,jvm仍然会使用classLoader加载这个类
示例代码
都是一些最简单的代码,代码不做过多介绍。
Test1
public class Test1 {
public static String name = initName();
static {
System.out.println("Test1.static code");
System.out.println("Test1.name:" + name);
}
private static String initName() {
System.out.println("Test1.initName()");
return "admin";
}
@TestAnnotation(arg = 12)
public void test1() {
System.out.println("Test1.test1()");
Test2 test2 = new Test2();
test2.test2();
}
}
Test2
public class Test2 {
public void test2() {
System.out.println("Test2.test2()");
}
}
TestAnnotation
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
int arg();
}
TestUrlClassLoader
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
public class TestUrlClassLoader {
public static void main(String[] args) throws Exception {
File file = new File(args[0]);
URLClassLoader urlClassLoader = new MyUrlClassLoader(new URL[]{file.toURI().toURL()});
Class<?> clazz = Class.forName("Test1", false, urlClassLoader);
Method method = clazz.getMethod("test1");
try {
method.getAnnotation(TestAnnotation.class);
} catch (Throwable e) {
e.printStackTrace();
}
Class<Annotation> testAnnotation =
(Class<Annotation>) Class.forName("TestAnnotation", false, urlClassLoader);
Annotation annotation = method.getAnnotation(testAnnotation);
Method arg = testAnnotation.getDeclaredMethod("arg");
Object invoke = arg.invoke(annotation);
System.out.println("TestAnnotation.value:" + invoke);
}
public static class MyUrlClassLoader extends URLClassLoader {
public MyUrlClassLoader(URL[] urls) {
super(urls);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("MyURLClassLoader.loadClass:" + name);
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("MyURLClassLoader.findClass:" + name);
return super.findClass(name);
}
}
}
运行示例
编译文件
[root@docker-0 test-urlclassloader]
Note: TestUrlClassLoader.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
[root@docker-0 test-urlclassloader]
[root@docker-0 bin]
Test1.class Test2.class TestAnnotation.class
TestUrlClassLoader.class TestUrlClassLoader$MyUrlClassLoader.class
创建jar文件
[root@docker-0 bin]
added manifest
adding: Test1.class(in = 963) (out= 570)(deflated 40%)
adding: Test2.class(in = 393) (out= 275)(deflated 30%)
adding: TestAnnotation.class(in = 453) (out= 266)(deflated 41%)
[root@docker-0 bin]
META-INF/
META-INF/MANIFEST.MF
Test1.class
Test2.class
TestAnnotation.class
[root@docker-0 bin]
[root@docker-0 bin]
test.jar TestUrlClassLoader.class TestUrlClassLoader$MyUrlClassLoader.class
运行示例
[root@docker-0 bin]
MyURLClassLoader.loadClass:Test1
MyURLClassLoader.findClass:Test1
MyURLClassLoader.loadClass:java.lang.Object
java.lang.NoClassDefFoundError: TestAnnotation
at TestUrlClassLoader.main(TestUrlClassLoader.java:21)
Caused by: java.lang.ClassNotFoundException: TestAnnotation
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
MyURLClassLoader.loadClass:TestAnnotation
MyURLClassLoader.findClass:TestAnnotation
MyURLClassLoader.loadClass:java.lang.annotation.Annotation
MyURLClassLoader.loadClass:java.lang.annotation.Target
MyURLClassLoader.loadClass:java.lang.annotation.Retention
MyURLClassLoader.loadClass:java.lang.annotation.RetentionPolicy
MyURLClassLoader.loadClass:java.lang.annotation.Documented
MyURLClassLoader.loadClass:java.lang.reflect.Proxy
MyURLClassLoader.loadClass:java.lang.Throwable
MyURLClassLoader.loadClass:java.lang.RuntimeException
MyURLClassLoader.loadClass:java.lang.Error
MyURLClassLoader.loadClass:java.lang.reflect.UndeclaredThrowableException
MyURLClassLoader.loadClass:java.lang.ClassNotFoundException
MyURLClassLoader.loadClass:java.lang.NoSuchMethodException
MyURLClassLoader.loadClass:java.lang.NoSuchMethodError
MyURLClassLoader.loadClass:java.lang.NoClassDefFoundError
MyURLClassLoader.loadClass:java.lang.reflect.InvocationHandler
MyURLClassLoader.loadClass:java.lang.Class
MyURLClassLoader.loadClass:java.lang.Integer
TestAnnotation.value:12