为什么打破双亲委派模型?

2022-08-23  本文已影响0人  糯米团子123
  1. 双亲委派模型:如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类加载器,父类加载器还存在父类加载器则进一步向上委托,依次递归,请求最终到达顶层启动类加载器。如果父类加载器可以完成类加载任务,就成功返回,父类加载器无法完成加载任务,则子类加载器会尝试自己去加载。

  2. 双签委派模型优缺点:
    2.1 优点
    避免重复加载,确保类的全局唯一性
    保护程序安全,防止核心api被随意篡改
    2.2 缺点
    由于加载范围限制,顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

  3. 为什么要破坏双亲委派模型?
    由于加载范围的限制,顶层的ClassLoader无法访问底层ClassLoader所加载的类,此时需要破坏双亲委派模型。

  4. 以JDBC为例
    4.1 不使用java SPI时,以如下方式加载驱动实现类:
    在Driver类中像DriverManager注册对应的驱动实现类。

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.
getConnection("jdbc:mysql://myhost/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "test", "test");

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

4.2 使用java SPI
JDBC4.0以后,开始支持使用SPI的方式来注册Driver,具体做法:在Mysql的jar包中的META-INF/services/java.sql.Driver文件中指明当前使用的Driver是哪个。
SPI:策略模式,根据配置决定运行时的接口实现类是哪个。当使用不同驱动时候,我们不需要手动通过Class.forName加载驱动类,只需要引入相应jar即可。

Connection conn = DriverManager.
getConnection("jdbc:mysql://myhost/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "test", "test");

4.2.1 驱动类何时加载?

public class DriverManager {

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {

        // 省略部分代码
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                // 根据配置文件加载驱动实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        // 省略部分代码
    }

}


ServiceLoader:通过执行Thread.currentThread().getContextClassLoader()获取了线程上下文加载器

// ServiceLoader#load
public static <S> ServiceLoader<S> load(Class<S> service) {

    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

ExtClassLoader和AppClassLoader都是通过Launcher类来创建的,在Launcher类的构造函数中,可以看到线程上下文类加载器默认设置了AppClassLoader

public class 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 {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        // 设置线程上下文类加载器为AppClassLoader
        Thread.currentThread().setContextClassLoader(this.loader);

        // 省略部分代码

    }
}

线程上下文类加载器让父类加载器能通过调用子类加载器来加载类,这打破了双亲委派模型的原则

上一篇 下一篇

猜你喜欢

热点阅读