Java自定义类加载器

自定义类加载器的应用场景

  • 加密:如果你不想自己的代码被反编译的话。(类加密后就不能再用ClassLoader进行加载了,这时需要自定义一个类加载器先对类进行解密,再加载)。

  • 从非标准的来源加载代码:如果你的字节码存放在数据库甚至是云端,就需要自定义类加载器,从指定来源加载类。

双亲委派

  • 我们先看一下ClassLoader类默认的loadClass方法实现
protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
                //父类加载器无法完成类加载请求
            }
            if (c == null) {
                // If still not found, then invoke findClass in order to find the class
                //子加载器进行类加载 
                c = findClass(name);
            }
        }
        if (resolve) {
            //判断是否需要链接过程,参数传入
            resolveClass(c);
        }
        return c;
    }

双亲委派模型工作过程如下:

(1) 类加载器从已加载的类中查询该类是否已加载,如果已加载则直接返回。

(2)如果在已加载的类中未找到该类,则委托给父类加载器去加载c = parent.loadClass(name, false),父类也会采用同样的策略查看自己加载的类中是否包含该类,如果没有则委托给父类,以此类推一直到启动类加载起。

(3)如果启动类加载器加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用拓展类加载器来尝试加载,继续失败则会使用AppClassLoader来加载,继续失败则会抛出一个异常ClassNotFoundException,然后再调用当前加载器的findClass()方法进行加载。

双亲委派的好处:

(1)避免自己编写的类动态替换java的核心类,比如String

(2)避免了类的重复加载,因为JVM区分不同类的方式不仅仅根据类名,相同的class文件被不同的类加载器加载产生的是两个不同的类。

正题:自定义类加载器

从上面的源码可以看出调用classLoader时会先根据委派模型在父类加在其中加载,如果加载失败则会加载当前加载器的findClass方法来加载,
因此我们自定义的类加载器只需要继承ClassLoader,并覆盖findClass方法。

  • 准备一个class文件,编译后放到D盘根目录下
public class People {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}
  • 自定义类加载器MyClassLoader ,继承ClassLoader覆盖findClass方法(其中defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
 
public class MyClassLoader extends ClassLoader
{
    public MyClassLoader(){}
    
    public MyClassLoader(ClassLoader parent)
    {
        super(parent);
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
    	File file = new File("D:/People.class");
        try{
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    
    private byte[] getClassBytes(File file) throws Exception
    {
        // 这里要读入.class的字节,因此要使用字节流
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);
        
        while (true){
            int i = fc.read(by);
            if (i == 0 || i == -1)
            break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }
}
  • 在主函数中测试一下
MyClassLoader mcl = new MyClassLoader(); 
Class<?> clazz = Class.forName("People", true, mcl); 
Object obj = clazz.newInstance();

System.out.println(obj);
//打印出我们的自定义类加载器
System.out.println(obj.getClass().getClassLoader());

参考链接:
www.cnblogs.com/gdpuzxs/p/7…
blog.csdn.net/seu_calvin/…

    原文作者:算法小白
    原文地址: https://juejin.im/entry/5be1346ee51d4570934d7fc4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞