NiceLeeのBlog 用爱发电 bilibili~

Java 关于类加载器ClassLoader的有趣实验

2020-02-03
nIceLee

阅读:


做个笔记,马克一下。

原理(大概)

类加载器的分类

  • 启动类加载器(Bootstrap ClassLoader):
    • 主要负责加载<JAVA_HOME>\lib目录,或是-Xbootclasspath参数指定的路径中的类库到虚拟机内存中。
    • System.getProperty("sun.boot.class.path")
  • 扩展类加载器(Extension ClassLoader):
    • 主要负责加载<JAVA_HOME>\lib\ext目录,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
    • System.getProperty("java.ext.dirs")
  • 应用程序类加载器/系统类加载器(Application ClassLoader):
    • 主要负责加载ClassPath路径上的类库,是默认的类加载器。
    • System.getProperty("java.class.path")

类的加载

  • 类加载器的双亲委派模型
    • 不多说了,启动类加载器优先级最高,它找到了加载类就是加载的它指定的那个了。扩展类加载器次之,系统类加载器最次。

关于Xbootclasspath参数

  • -Xbootclasspath:
    • 完全取代基本核心的Java class 搜索路径。要重新写所有Java 核心class
  • -Xbootclasspath/a:
    • 后缀在核心class搜索路径后面
  • -Xbootclasspath/p:
    • 前缀在核心class搜索路径前面

自定义类加载器

继承自java.lang.ClassLoader类并覆写对应的方法即可。

有趣的用途(大概)

Hook某些java应用

比如,现在我要hook原生的输出方法java.io.PrintStream.println()
当我调用这个方法的时候,会输出hhhhhhh再加上换行符。

  • 代码框架如下:
  • java.io.PrintStream的改动如下,其它原样复制
  • 入口测试类:

      package test;
    
      public class TestPrint {
    
          public static void main(String[] args) {
              System.out.println("----TestPrint begin----");
              System.out.println();
              System.out.println("----TestPrint end----");
          }
    
      }
    
  • 接下来不多说,直接放结果
    • java -jar test.jar
    • java -Xbootclasspath/p:test.jar -jar test.jar

动态编译/加载(刷题实现原理?

怎样做一个像LeetCode那样的Java刷题功能呢?

  • 一个简单的方法是:
    • 将提交的题目编译.class文件到当前ClassPath路径下
    • 系统类加载器直接加载类
    • 通过反射调用约定的方法,得到结果
    • 比较预期和实际结果,返回响应

    • 下面是一个例子:来自java实现动态编译并动态加载
        public void complierAndRun() {
        try {
            System.out.println(System.getProperty("user.dir"));
            // 动态编译
            JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
            int status = javac.run(null, null, null, "-d", System.getProperty("user.dir") + "\\target\\classes", "D:/test/AlTest.java");
            if (status != 0) {
                System.out.println("没有编译成功!");
            }
      
            // 通过系统类加载器直接加载类
            Class clz = Class.forName("AlTest");
            // 通过反射动态执行
            Object o = clz.newInstance();
            Method method = clz.getDeclaredMethod("sayHello");
            // method.invoke() :静态方法第一个参数可为null,第二个参数为实际传参
            String result = (String) method.invoke(o);
            System.out.println(result);
        } catch (Exception e) {
            logger.error("test", e);
        }
        }
      
  • 当用户/题目较多时,显然需要再变更一下,稍微再复杂一点:
    • 将提交的题目编译.class文件到指定路径下
    • 自定义ClassLoader加载类
    • 通过反射调用约定的方法,得到结果
    • 比较预期和实际结果,返回响应

    • 下面是大概的要点:
        public class MyClassLoader extends ClassLoader {   
              
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 读取name对应.class文件到b
            byte[] b = null;
            ...
            return defineClass(name, b, 0, b.length);
        }
      
        public static void main(String[] args) {
            ...
            try {
                // 重载ClassLoader类
                MyClassLoader loader = new MyClassLoader();
                Class<?> c = loader.findClass(className);// 类名字
                c.getMethod(methodName).invoke(c.newInstance());// 方法名字
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        }
      

内容
隐藏