自定义类加载器的应用场景
加密:如果你不想自己的代码被反编译的话。(类加密后就不能再用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());