java类隔离机制实现

509 阅读2分钟

类隔离机制

在上一节中,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类隔离加载?