
课程咨询: 400-996-5531 / 投诉建议: 400-111-8989
认真做教育 专心促就业
昆明达内的小编这一期给大家讲自定义类加载器:
自定义类加载器时不需要在自己写双亲委派的逻辑,因此不鼓励重写loadClass方法,而推荐重写findClass方法。
在Java中,任意一个类都需要由加载它的类加载器和这个类本身一同确定其在java虚拟机中的唯一性,即比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,只要加载它的类加载器不相同,那么这两个类必定不相等(这里的相等包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof关键字的结果)。
被加载的类:
package com.example.test;
public class Animal {
public String say(String content){
System.out.println("say:"+content);
return "";
}
}
自定义类加载器:
package com.example.test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
class MyClassLoader extends ClassLoader {
@Override
public Class loadClass(String name) throws ClassNotFoundException {
//检查,是否该类已经加载过了,如果加载过了,就不加载了
Class c = findLoadedClass(name);
//如果未加载,则进行以下操作
if (c == null) {
//如果该类存在于当前类加载路径下,则使用自定义加载器加载,如果不存在于当前路径,则使用父类加载器
//(如加载Animal类时使用自定义,但是加载Animal也需要加载它的父类Object,这个时候就要使用父类加载器【根类加载器】)
File file = new File(getFileName(name));
if (file.exists()) {
//调用自定义的方法
return this.findClass(name);
} else {
//调用父类加载器
return super.loadClass(name);
}
} else {
return c;
}
}
@Override
public Class findClass(String name) {
System.out.println("正在使用自定义类加载器加载类:" + name);
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
try {
//获取类文件的IO
FileInputStream is = new FileInputStream(new File(getFileName(name)));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//获取类文件的全路径
private String getFileName(String name) {
//将包名间隔符改为路径间隔符
name = name.replace(".", "//");
//类存放路径
String path = MyClassLoader.getSystemClassLoader().getResource("").getPath();
String fileName = path + name + ".class";
return fileName;
}
}
测试类:
package com.example.test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
MyClassLoader cl = new MyClassLoader();
Class clz = cl.loadClass("com.example.test.Animal");
Method method = clz.getMethod("say", String.class);
Object obj = clz.newInstance();
method.invoke(obj, "hello");
System.err.println("类:" + obj + ",使用的类加载器是:" + obj.getClass().getClassLoader());
}
}
运行结果:
正在使用自定义类加载器加载类:com.example.test.Animal
say:hello
类:com.example.test.Animal@a09ee92,使用的类加载器是:com.example.test.MyClassLoader@27716f4
类加载器双亲委派模型是从JDK1.2以后引入的,并且只是一种推荐的模型,不是强制要求的,因此有一些没有遵循双亲委派模型的特例:
(1).在JDK1.2之前,自定义类加载器都要覆盖loadClass方法去实现加载类的功能,JDK1.2引入双亲委派模型之后,loadClass方法用于委派父类加载器进行类加载,只有父类加载器无法完成类加载请求时才调用自己的findClass方法进行类加载,因此在JDK1.2之前的类加载的loadClass方法没有遵循双亲委派模型,因此在JDK1.2之后,自定义类加载器不推荐覆盖loadClass方法,而只需要覆盖findClass方法即可。
(2).双亲委派模式很好地解决了各个类加载器的基础类统一问题,越基础的类由越上层的类加载器进行加载,但是这个基础类统一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类加载器进行类加载。为了解决这个问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器。
JavaEE只是一个规范,sun公司只给出了接口规范,具体的实现由各个厂商进行实现,因此JNDI,JDBC,JAXB等这些第三方的实现库就可以被JDK的类库所调用。线程上下文类加载器也没有遵循双亲委派模型。
(3).近年来的热码替换,模块热部署等应用要求不用重启java虚拟机就可以实现代码模块的即插即用,催生了OSGi技术,在OSGi中类加载器体系被发展为网状结构。OSGi也没有完全遵循双亲委派模型。