类隔离机制
在上一节中,java通过双亲委派机制来确定类文件加载的唯一。但是在实际应用中很难保证maven管理的三方包没有冲突,尤其是tomcat这样可以搭载多个应用的容器,是需要容许冲突存在,这时java自带的类加载机制就不再合适,需要打破双亲委派机制,从而实现类隔离。
jvm层面类隔离实现的可能
因为以前以为jvm依靠全限定类名区分类,但是实际上不止通过全限定类名还有类加载器。在jvm中,SystemDictionary记录了所有加载的类,其中依靠类加载器+全限定类名为key区分类。
所以即使类名相同,因为classloader不同造成ClassCastException抛出。
solution for the classcastexception due to classloader issue
在了解JVM加载类的机制后不难想出,用同一个加载器就不会有问题了除了读取class文件意外,我们还可以用序列化的方式来加载write: ByteArrayOutputStream bos = new ByteArrayOutputstream(); ObjectOutput out = null; try{ out = new ObjectOutput(bos); out.writeObjecct(cachedVaule); byte b[] = bos.toByteArray(); // Store in DB, file wherever here using b[] } catch(IOException e) { ... } read: ByteArrayInputStream bis = new ByteArrayInputStream(); ObjectInput in = null; try{ in = new ObjectInput(bis); <Your class> cachedres = (Your class) in.readObject(); }catch(Exception e) { ... }
如何自定义classloader实现类隔离
编写自定义classloader需要注意两点
- 保证核心库java类加载相同
- 加载类与其依赖类加载均由相同加载器加载
这里着重说明一下第二条。我们需要实现一种类加载传导规则:JVM 会选择当前类的类加载器来加载所有该类的引用的类,也就是说需要所加载的类及其引用类关联的所有 jar 包的类都会被同一加载器加载。这样一个classloader可以独立加载一个module,从而实现类隔离。自定义代码实现如下
public class SxmClassLoaderCustom extends ClassLoader {
private ClassLoader jdkClassLoader;
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class result = null;
try {
//这里要使用 JDK 的类加载器加载 java.lang 包里面的类
result = jdkClassLoader.loadClass(name);
} catch (Exception e) {
//忽略
}
if (result != null) {
return result;
}
String path = name.replace('.', File.separatorChar) + ".class";
URL url = getSystemClassLoader().getResource(path);
File file = new File(url.getPath());
if (!file.exists()) {
throw new ClassNotFoundException();
}
byte[] classBytes = getClassData(file);
if (classBytes == null || classBytes.length == 0) {
throw new ClassNotFoundException();
}
return defineClass(classBytes, 0, classBytes.length);
}
private byte[] getClassData(File file) {
try (InputStream ins = new FileInputStream(file); ByteArrayOutputStream baos = new
ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[] {};
}
}
这里看到loadclass首先仍然沿用了双亲加载机制,保证核心类库的唯一加载。不过需要注意的是,jdkClassLoader不能是appClassLoader,要appClassLoader的parent:extClassLoader。
SxmClassLoader cl = new SxmClassLoader(getSystemClassLoader().getParent());
参考文献:
[1]: 源码解读:Java方法main在虚拟机上解释执行 [2]: 深入浅出ClassLoader(译) [3]:如何实现Java类隔离加载?