• JAVA基础知识之JVM-——自定义类加载器


    JVM中除了根加载器之外其他加载器都是ClassLoader的子类实例, 可以通过扩展ClassLoader的子类,通过重写方法来实现自定义的类加载器。

    ClassLoader中有两个关键的方法如下,

    • loadClass(...), 系统调用这个方法来加载指定类的Class对象

      在这个方法中,一般需要做四件事,先后顺序如下,

    1. findLoadedClass(..)看是否已经加载类——加载器的缓存机制
    2. 在父加载器上调用loadClass(..), 父加载器为null,则使用根加载器加载——加载器的父类委托机制
    3. 调用findClass方法查找类
    • findClass(...),根据名称查找类

    ClassLoader中还有一个核心方法 Class defineClass(...),它负责将class文件读入字节数组,并把它转为Class对象。

    所以如果需要自定义加载器,只需要重写findClass(...)方法就行了。因为loadClass不仅用到了缓存机制和父类委托机制,还直接调用了findClass类。

    下面是一个简单例子来实现自定义加载器, 这个程序通过重写findClass实现自定义类的加载机制,让它在加载之前先编译源文件,这样就实现运行java文件之前先编译它, 从而可以通过这个程序直接执行java源文件。

      1 import java.io.File;
      2 import java.io.FileInputStream;
      3 import java.io.IOException;
      4 import java.lang.reflect.InvocationTargetException;
      5 import java.lang.reflect.Method;
      6 
      7 public class MyClassLoader extends ClassLoader{
      8     // 读取一个文件的内容
      9     private byte[] getBytes(String filename) throws IOException {
     10         File file = new File(filename);
     11         long len = file.length();
     12         byte[] raw = new byte[(int)len];
     13         try {
     14             FileInputStream fin = new FileInputStream(file);
     15             //一次读取class文件的全部二进制数据
     16             int r = fin.read(raw);
     17             if (r != len) {
     18                 throw new IOException("无法读取全部文件: "+r+" != "+raw);
     19             }
     20             return raw;
     21         } catch (IOException e) {
     22             throw e;
     23         }
     24     }
     25     
     26     //定义编译指定java文件的方法
     27     private boolean compile(String javaFile) throws IOException{
     28         System.out.println("MyClassLoader: " + "正在编译 " + javaFile);
     29         Process p = Runtime.getRuntime().exec("javac " + javaFile);
     30         try {
     31             //其他线程都等这个线程完成
     32             p.waitFor();
     33         } catch (InterruptedException e) {
     34             System.out.println(e);
     35         }
     36         //获取javac线程的退出值
     37         int ret = p.exitValue();
     38         return ret == 0;
     39     }
     40     
     41     //重写ClassLoader的findClass方法
     42     protected Class<?> findClass(String name) throws ClassNotFoundException {
     43         Class clazz = null;
     44         // 将包路径中的点(.)替换成斜杠(/)
     45         String fileStub = name.replace(".", "/");
     46         String javaFilename = fileStub + ".java";
     47         String classFilename = fileStub + ".class";
     48         File javaFile = new File(javaFilename);
     49         File classFile = new File(classFilename);
     50         //当指定java源文件存在,且class文件不存在,或者java源文件的修改时间比class文件的修改文件更晚时,重新编译
     51         if (javaFile.exists() && (!classFile.exists()
     52                 || javaFile.lastModified() > classFile.lastModified())) {
     53             try {
     54                 //如果编译失败,或者该class不存在
     55                 if (!compile(javaFilename) || !classFile.exists()) {
     56                     throw new ClassNotFoundException("ClassNotFoundException " + javaFilename);
     57                 }
     58             } catch (IOException e) {
     59                 e.printStackTrace();
     60             }
     61         }
     62         
     63         //如果class文件存在,将其转换为Class对象
     64         if (classFile.exists()) {
     65             try {
     66                 //将class文件的二进制数据读入数组
     67                 byte[] raw = getBytes(classFilename);
     68                 //调用ClassLoader的defineClass方法将class文件转换成Class对象
     69                 clazz = defineClass(name, raw, 0, raw.length);
     70             } catch (IOException e) {
     71                 e.printStackTrace();
     72             }
     73         }
     74         
     75         // 如果clazz为null, 表明失败, 则抛出异常
     76         if (clazz == null) {
     77             throw new ClassNotFoundException(name);
     78         }
     79         
     80         return clazz;
     81     }
     82     
     83     public static void main(String[] args) throws Exception {
     84         // 如果运行该程序时没有参数,即没有目标类
     85         
     86         if (args.length < 1) {
     87             
     88             System.out.println("缺少目标类,请安如下格式运行Java源文件");
     89             System.out.println("java MyClassLoader ClassName");
     90         }
     91         
     92         //第一个参数是要运行的类
     93         String progClass = args[0];
     94 
     95         //剩下的参数将做为目标运行时的参数
     96         //将这些参数复制到一个新的数组中
     97         String[] progArgs = new String[args.length - 1];
     98         System.arraycopy(args, 1, progArgs, 0 , progArgs.length);
     99         MyClassLoader mcl = new MyClassLoader();
    100         //加载需要运行的类
    101         Class<?> clazz = mcl.loadClass(progClass);
    102         //获取需要运行类的主方法
    103         Method main = clazz.getMethod("main", (new String[0]).getClass());
    104         Object argsArray[] = {progArgs};
    105         main.invoke(null, argsArray);
    106     }
    107 }

    下面写一个测试用的类,

    1 public class Test{
    2     public static void main(String[] args) throws Exception {
    3         System.out.println("this is Test");
    4     }
    5 }

    使用 java MyClassLoader Test 执行程序

    自定义类加载器的意义

    自定义加载类,还能实现以下重要功能,

    • 执行代码前自动验证代码签名
    • 根据用户提供的密码解密代码, 从而实现代码混淆器来防止代码被反编译*.class文件
    • 根据需求动态地加载类
    • 根据需求把其他数据以字节码的形式加载到应用中
  • 相关阅读:
    JSP(7)—EL和JSTL
    JSP(6)—JavaBean及案例
    JSP(5)—Session的创建以及简单使用
    JSP(4)—Cookie创建及简单案例(自动登录)
    JSP(3)—Cookie和Session
    JSP(2)—绝对路径与相对路径、配置Servlet与Servlet注解
    JSP(1)—基础知识
    Dockerfile RUN mkdir xxx 的时候报了Permission denied
    摘抄:Solr和ElasticSearch的区别
    POST请求反向代理设置
  • 原文地址:https://www.cnblogs.com/fysola/p/6099519.html
Copyright © 2020-2023  润新知