学无先后,达者为师

网站首页 编程语言 正文

URLClassLoader加载Class时的类初始化问题

作者:xuguofeng2016 更新时间: 2022-07-22 编程语言

本文将通过一个示例代码说明URLClassLoader加载Class时的几个问题:

  1. Class.forName(…)方法确实是使用传递的classLoader加载目标类
  2. initialize参数传递false不会初始化静态资源
  3. 使用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

下面我们使用一个示例程序证明一下以下几个问题:

  1. Class.forName(…)方法确实是使用传递的classLoader加载目标类
  2. initialize参数传递false不会初始化静态资源
  3. 使用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]);

    // 使用自定义的MyUrlClassLoader加载外部jar文件
    URLClassLoader urlClassLoader = new MyUrlClassLoader(new URL[]{file.toURI().toURL()});

    // 使用MyUrlClassLoader加载Test1
    // 不初始化
    Class<?> clazz = Class.forName("Test1", false, urlClassLoader);
    // 获取test1方法
    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);

    // 以下创建Test1对象,这里一定会做初始化
    // Constructor constructor = clazz.getConstructor();
    // Object o = constructor.newInstance();
    // method.invoke(o);
  }

  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]# javac -d ./bin *.java
Note: TestUrlClassLoader.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
[root@docker-0 test-urlclassloader]# cd bin
[root@docker-0 bin]# ls
Test1.class  Test2.class  TestAnnotation.class
TestUrlClassLoader.class  TestUrlClassLoader$MyUrlClassLoader.class

创建jar文件

[root@docker-0 bin]# jar -cvf test.jar Test1.class Test2.class TestAnnotation.class
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]# jar -tf test.jar
META-INF/
META-INF/MANIFEST.MF
Test1.class
Test2.class
TestAnnotation.class
[root@docker-0 bin]# rm -f Test1.class Test2.class TestAnnotation.class
[root@docker-0 bin]# ls
test.jar  TestUrlClassLoader.class  TestUrlClassLoader$MyUrlClassLoader.class

运行示例

[root@docker-0 bin]# java TestUrlClassLoader ./test.jar
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

原文链接:https://blog.csdn.net/xuguofeng2016/article/details/125919434

栏目分类
最近更新