开发中的类几乎由上述3种类加载器互相配合加载,必要时可以自定义类加载器来指定类的加载方式
为什么要自定义类加载器
隔离加载器
多个框架包名和类型相同,类冲突,把类加载到不同的应用选中
如tomcat,内部自定义了好几中类加载器,用于隔离web应用服务器上的不同应用程序。
修改类加载的方式
除了Bootstrap加载器外,其他的加载并非一定要引入,根据实际情况在某个时间按需进行动态加载
扩展加载器:比如还可以从数据库、网络、或其他终端上加载
防止源码泄漏: java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。
实现步骤
(1) 通过继承抽象类java.lang.ClassLoader的方式,实现自己的类加载器
(2) 在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loaderClass()方法,从而实现自定义的类加载器, 但是在JDK1.2之后不再建议用户去覆盖loadClass()方法,在loadClass()里面有双亲委派机制的代码,而是建议把自定义的类加载逻辑写在findClass()方法中
(3) 在编写自定义类加载器时,若没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁
public class MyClassLoader extends ClassLoader {
// 字节码文件的路径
private String codePath;
public MyClassLoader(ClassLoader parent, String codePath) {
super(parent);
this.codePath = codePath;
}
public MyClassLoader(String codePath) {
this.codePath = codePath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
String file = codePath + name + ".class";
bis = new BufferedInputStream(new FileInputStream(file));
int len;
byte[] data = new byte[1024];
while ((len = bis.read(data)) != -1){
baos.write(data,0,len);
}
// 获取内存中的字节数组
byte[] bytes = baos.toByteArray();
// 调用defineClass将字节数组转成Class实例
Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
// 返回class对象
return clazz;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
测试
public class MyClassLoaderTest {
public static void main(String[] args) {
// 在这个路径上放一个Test.class,注意不是Test.java.注意项目中不要写Test类
MyClassLoader classLoader = new MyClassLoader("d:/");
try {
Class<?> clazz = classLoader.loadClass("Test");
System.out.println("Test字节码是由" + clazz.getClassLoader().getClass().getName() + "加载的");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}