类加载器一般分为:
启动类加载器BootstrapClassLoader
扩展类加载器ExtClassLoader
系统类加载器AppClassLoader
用户自定义类加载器
ExtClassLoader和AppClassLoader是Launcher的内部类,都继承了URLClassLoader,最上层的基类为ClassLoader。
那么为什么说AppClassLoader的父类是ExtClassLoader呢?看Launcher的构造方法的源码:
public Launcher() {
Launcher.ExtClassLoader var1;// 这里设置扩展类加载器对象
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 这里构造AppClassLoader,并把ExtClassLoader传了进去
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
...
}
再看AppClassLoader的构造方法
AppClassLoader(URL[] var1, ClassLoader var2) {
// var2是ExtClassLoader,这里调用了父类的构造方法
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
进入父类的构造方法
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
..
}
//继续
protected SecureClassLoader(ClassLoader parent) {
super(parent);
..
}
//继续
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;//在这里设置的父类,即ExtClassLoader
}
同理ExtClassLoader的父类也是这样被设置为null.
类加载时候会判断当前类加载器的父类是否空,
为空则调用本地方法使用BootstrapClassLoader类加载器,
不为空则调用父类加载器
引入了两个中间件A和B,A有xxjar,B有xxjar,但是版本不同,包结构完全一致,怎么实现版本隔离呢?
需要对一个全限定名称的类加载两个不同的版本,但是由于JVM类加载的时候使用了双亲委派机制,该机制会导致类加载顺序如下:
BootstrapClassLoader启动类加载器进行加载,
ExtClassLoader扩展类加载器进行加载,
AppClassLoader系统类加载器进行加载,
最后是用户自定义类加载器进行加载。
这个机制会导致一个问题,就是一个类不能重复加载两次,那么如何实现版本隔离呢?
首先判断一个类是否相同,要满足两个条件:
即类的全限定名一样并且类加载器也要一样
那么我们就可以自己写一个类加载器,这样的话类加载器就不一样了,但是依旧逃不了双亲委派机制,
所以我们需要破坏双亲委派机制,看ClassLoader类加载器加载类的源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 这里通过一个递归来实现双亲委派机制加载顺序的
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
...
}
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
...
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
在源码中我们可以看到在双亲委派机制的作用下每次加载一个类都会进行检查,去判断c是否为null(即类没有被加载过),
这种情况才会调用findClass方法,该方法由子类UrlClassLoader进行了重写,可以看源码findClass方法最终调用了ClassLoader的defineClass方法,该方法会调用ClassLoader的本地方法创建一个类。
综上所述,我们想要破坏双亲委派机制,那么就要越过loadClass方法中检测的阶段或者使它无效,在loadClass中其实最核心的还是调用了findClass方法,当前面的三个类加载器都加载不到时,就会调用ClassLoader的findClass方法,因此我们可以定义一个类加载器,直接继承ClassLoader,然后重写findClass方法,就可以用我们自定义的类加载器去同名的class类,demo如下:
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
/**
* 编写findClass方法的逻辑
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类的class文件字节数组
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
//直接生成class对象
return defineClass(name, classData, 0, classData.length);
}
}
/**
* 编写获取class文件并转换为字节码流的逻辑
* @param className
* @return
*/
private byte[] getClassData(String className) {
// 读取类文件的字节
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 读取类文件的字节码
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 类文件的完全路径
* @param className
* @return
*/
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
public static void main(String[] args) throws ClassNotFoundException {
String rootDir="d:\\";
//创建自定义文件类加载器
MyClassLoader loader = new MyClassLoader(rootDir);
try {
//加载指定的class文件
Class<?> object1=loader.loadClass("json.Person");
System.out.println(object1.newInstance().toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}