首先一个问题:为什么要自定义类加载器?
我们大家都知道,java内部已经有3大加载器,可以满足大部分类的加载场景了,为什么还要来自定义加载器呢?这个问题,我们来分析一下当前类加载的一些弊端。
-
非当前classpath下的类,我们当前的类加载器是无法进行加载的,如果在数据库,在远程服务器上的class,等等
-
我们当前类为了更加的安全,我们需要对其进行加密操作
以上的问题,当前的类加载是无法处理,所以我们需要自己自定义类加载。
那么怎么自定义类加载器呢?
首先,分析ClassLoader类的加载方法
//双亲委派模型的工作过程源码
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) {
//父类加载器无法完成类加载请求
}
if (c == null) {
//子加载器进行类加载
c = findClass(name);
}
}
if (resolve) {
//判断是否需要链接过程,参数传入
resolveClass(c);
}
return c;
}
这段代码就是双亲委派机制的最核心的代码,上几篇笔者都有讲过,这里就不展开讲了。 今天,主要讲一下**findclass(name)**这个方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
找到这段代码,发现没有实现体,说话这个需要子类来进行实现。
分析到这里,我们得出了一个结论:
从上面源码看出,调用loadClass时会先根据委派模型在父加载器中加载,如果加载失败,则会调用当前加载器的findClass来完成加载。
因此我们自定义的类加载器只需要继承ClassLoader,并覆盖findClass方法,下面是一个实际例子,在该例中我们用自定义的类加载器去加载我们事先准备好的class文件。
自定义类加载器开始
1 第一步:继承ClassLoader
defineClass方法可以把二进制流字节组成的文件转换为一个java
package com.shendu;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
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:/User.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 IOException {
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();
}
}
2 定义user类
public class User {
private String name = "shendu-sir";
private Integer age = 18;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
3 把User类放在D盘下面,用javac 命令将User编译成字节码文件 PS:User一定不要放在类路径下,因为双亲委托机制,这个User类会永远的被AppClassLoader加载。 我们自定义的加载器永远加载不到
4 编写测试类
package com.shendu;
public class JvmTest03 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> user = myClassLoader.loadClass("User");
Object o = user.newInstance();
System.out.println(o);
System.out.println(o.getClass().getClassLoader());
}
}
打印:
User{name='shendu-sir', age=18}
com.shendu.MyClassLoader@4554617c
Process finished with exit code 0
由此:从打印结果来看,User这个类是由我们自定义类加载器进行加载的。
预告:下一篇,讲解 如何打破双亲委派