MST

星途 面试题库

面试题:Java中如何打破双亲委派模型

在Java开发中,有时可能需要打破双亲委派模型,请阐述打破双亲委派模型的场景,以及具体通过哪些方式可以打破该模型,同时说明打破后可能带来的风险。
48.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

打破双亲委派模型的场景

  1. 实现热插拔、热部署:在一些框架如OSGi中,需要动态加载和替换类,打破双亲委派模型可以实现不同模块使用不同版本的类,避免版本冲突。
  2. 自定义类加载需求:某些特殊业务场景,如需要从特定位置(如数据库、网络等非常规路径)加载类,而不是按照双亲委派模型从常规的系统类路径加载。

打破双亲委派模型的方式

  1. 自定义类加载器:继承ClassLoader类,重写loadClass方法,在该方法中不调用父类的loadClass方法,而是自己实现类的加载逻辑。例如:
public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查该类是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 自己实现类加载逻辑,例如从指定路径读取class文件并转化为字节数组
                    byte[] classData = loadClassData(name); 
                    c = defineClass(name, classData, 0, classData.length);
                } catch (Exception e) {
                    // 如果自定义加载失败,再尝试使用父类加载器加载
                    c = getParent().loadClass(name); 
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    private byte[] loadClassData(String className) throws Exception {
        // 从自定义位置(如文件系统、网络等)读取class文件并返回字节数组
        // 示例:从文件系统读取
        File file = new File("custom-classes/" + className.replace('.', '/') + ".class");
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = fis.read(buffer)) != -1) {
            bos.write(buffer, 0, length);
        }
        fis.close();
        bos.close();
        return bos.toByteArray();
    }
}
  1. 线程上下文类加载器:通过Thread.currentThread().setContextClassLoader设置线程上下文类加载器,在需要打破双亲委派的地方,使用该线程上下文类加载器加载类。例如在JDBC的DriverManager中,就是通过线程上下文类加载器加载数据库驱动,因为数据库驱动通常是由第三方提供,不适合由启动类加载器或扩展类加载器加载。

打破后可能带来的风险

  1. 类冲突问题:可能会导致同一个类被不同的类加载器加载,从而在内存中存在多个不同的类实例,在进行类型比较、对象转换等操作时可能会出现异常。例如,一个类A被系统类加载器加载和自定义类加载器加载,这两个A类虽然类名相同,但实际是不同的类型,在进行instanceof判断等操作时会得到错误结果。
  2. 安全风险:打破双亲委派模型可能绕过系统类加载器的安全检查机制。系统类加载器通常会对加载的类进行一定的安全验证,自定义类加载器如果不进行同样严格的验证,可能会加载恶意类,从而导致安全漏洞,如恶意代码注入等问题。
  3. 维护复杂性增加:由于打破双亲委派模型后,类的加载逻辑变得复杂,不同模块可能使用不同的类加载器,这增加了代码维护的难度,在排查类加载相关问题时更加困难。