JavaEnhance——类加载器

一、概述

1.类加载器的定义

所有Java类在使用的时候都必须通过类加载器加载到内存。Java虚拟机可以安装多个类加载器,系统默认的,有三个主要的类加载器:BootStrap, ExtClassLoader, AppClassLoader。它们分别负责加载特定位置的类。

2.类加载器的性质

类加载器也是Java类。因此,类加载器也要被一个类加载器加载,才能工作。这么说来,必须有一个不是Java类的类加载,才能完成上述工作。这个类加载器就是BootStrap,它是所有类加载器的父类。

3.类加载器的继承关系

Java虚拟机中的所有类加载器都具有其父子关系的树形结构,在实例化每个加载器的对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载器。

类加载器继承关系

二、委托机制

在类加载器的委托机制中,子类类加载器都会先委托其父类类加载器去寻找要加载的类;如果父类加载器寻找到该类,就直接加载该类;如果没有找到,就会逐级返回,直到回到子类加载器,由子类类加载器加载该类。

三、自定义类加载器

自定义类加载器必须继承ClassLoader类,并复写findClass()方法。

提示:没有复写loadClass()方法的原因在于,loadClass()方法会找其父类加载器,如果父类加载器无法完成类的加载,loadClass()方法回返回来调用findClass()方法。如果复写loadClass()方法,那么去调用父类的实现还需要自己动手写,代码复杂。所以只需要复写findClass()方法即可。

下面的例子实现了用自定义类加载器加载一个类,并将这个类进行加密解密,也就是说,只有自定义类加载器才能调用该类。

需要被加密的类:

package com.heisejiuhuche.javaenhance;

import java.util.Date;

/**
 * 由于这个类是要被加密的,一旦类名出现在程序中,编译无法通过
 * 所以采取继承一个类的形式来调用这个类。
 * @author jeremy
 *
 */
public class ClassLoaderAttachment extends Date {
    /* 继承Date类,并复写toString()方法 */
    public String toString() {
        //一旦类加载成功,调用该类的toString()方法将打印Shit happens...
        return "Shit happens...";
    }
}

自定义类加载器:

package com.heisejiuhuche.javaenhance;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 自定义类加载器
 * 这个类可以加载ClassLoaderAttachment类,并完成了对ClassLoaderAttachment类的加密和解密
 * @author jeremy
 *
 */
public class MyClassLoader extends ClassLoader {
    /* 私有成员dir, 用于接收要加载类的路径 */
    private String dir;

    /* 无参构造方法 */
    public MyClassLoader() {}

    /* 接收加载类路径的构造方法 */
    public MyClassLoader(String dir) {
        this.dir = dir;
    }

    /* main方法完成对ClassLoaderAttachment类的加密和解密工作 */
    public static void main(String[] args) throws Exception {
        String src = args[0];
        /* 通过main()方法参数获取源文件绝对路径 */
        File srcPath = new File(src);
        /* 通过main()方法参数拼接目标文件的路径,这里是读取源文件,并存储到一个指定的目录下*/
        String dest = args[1] + File.separator + srcPath.getName();
        File destPath = new File(dest);
        /* 获取输入输出流 */
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);
        /* 对源文件进行加密 */
        encrypt(fis, fos);
        fis.close();
        fos.close();
    }

    /* 复写findClass方法 */
    @Override
    protected java.lang.Class<?> findClass(String arg0) throws ClassNotFoundException {
        try {
            /* 在loadClass()方法中只传递了要加载类不带后缀的名字,所以这里要拼接一个文件路径来加载该类 */
            String fileName = dir + File.separator + arg0 + ".class";
            FileInputStream fis = new FileInputStream(fileName);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            /* 解密 */
            encrypt(fis, bos);
            fis.close();
            byte[] buf = bos.toByteArray();
            /* 返回一个Class */
            return defineClass(buf, 0, buf.length);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    };

    /* 简单的加密算法,将读到的每个字节都取反 */
    private static void encrypt(InputStream is, OutputStream os) throws Exception {
        int b = 0;
        while((b = is.read()) != -1) {
            os.write(b ^ 0xff);
        }
    }
}

类加载器测试类:

package com.heisejiuhuche.javaenhance;

import java.util.Date;

/* 测试类测试自定义类加载器 */
public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        /* 得到解密之后的ClassLoaderAttachment类的字节码 */
        Class clazz = new MyClassLoader("heisejiuhuchelib").loadClass("ClassLoaderAttachment");
        /* 由于ClassLoaderAttachment类是加密的,不能直接调用,所以创建其实例对象后向上转型为Date类型*/
        Date d = (Date)clazz.newInstance();
        /* 调用toString()方法,打印Shit happens... */
        System.out.println(d.toString());
    }
}

程序输出结果:Shit happens...