其他分享
首页 > 其他分享> > 打破双亲委派的两种场景

打破双亲委派的两种场景

作者:互联网


theme: smartblue
highlight: androidstudio

  本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

前言

  昨天的文章JVM学习笔记P2—Java中的类加载器介绍了Java中的类加载机制,里面提到了双亲委派机制可以保证Java的运行安全,保证Java中的核心类可以被正确安全加载。那有没有打破双亲委派机制的Java应用呢?其实是有的,比如运行Java Web应用的Tomcat容器以及数据库操作中间件JDBC,它们就都打破了双亲委派机制了。为什么它们如此特殊呢?

Tomcat打破双亲委派

  通常使用Java开发Web应用时,最后我们都会将源码打包成一个war包或者jar包,然后扔进Tomcat容器里去运行。通常情况下,一台服务器上只会部署一个Tomcat容器,现在因为公司资源有限,我想在Tomcat里部署多个应用可不可以?肯定是可以的,只要server.port设置不同,就可以在Tomcat里部署多个应用。那现在问题来了,不同应用里可能使用了同一个jar包的不同版本,就比如常见的lombok,就有很多个不同的版本。如果Tomcat还是使用Java默认的双亲委派机制(注:Tomcat也是Java写的),那就只会加载一个lombok?那Tomcat到底加载哪个?还有就是如mysql-connector-java这种比较特殊的,如果mysql是5.7版本的,只能使用mysql-connector-java的特定版本来加载。此时如果Tomcat还是遵从双亲委派就无法支持这种场景

Tomcat的类加载机制

image.png

上图就是Tomcat中的类加载机制。可以看到,最顶层的三个类加载器还是Java中默认的类加载器。Common ClassLoaderCatalina ClassLoaderShared ClassLoaderWebapp ClassLoaderJasper Loader则是Tomcat内部自定义的类加载器。

JDBC打破双亲委派

  Java开发难免少不了与数据库打交道。而JDBCJava中与数据库连接的APIJDBCDriver是定义在rt.jar包中的,但是它的实现通常是由不同的数据库厂商来完成的,如mysql-connetor-java。同时rt.jar包中的DriverManager类会加载每个Driver接口的实现类并管理它们。那么问题来了,根据类加载机制,某个类需要引用其它类的时候,虚拟机将会用这个类的classloader去加载被引用的类。既然DriverManager是由BootStrap ClassLoader加载,那么各个Driver的实现类理应也由BootStrap ClassLoader来加载。但很显然,Boostrap Classloader显然是无法加载到mysql-connetor-java的,二者明显不在一个目录里,直接报ClassNotFound。因此只能在DriverManager里强行指定下层的classloader来加载Driver实现类,而这就会打破双亲委派模型

JDBC实现方式

  在JDBC 4.0之后,借助于JavaSPI机制,可以实现自动加载。通过查看DriverManager类的代码可以看到,当我们使用DriverManager的时候就会触发static代码块,进而会加载META-INF/services/java.sql.Driver指定的类。

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

private static void loadInitialDrivers() {
    AccessController.doPrivileged(new PrivilegedAction<Void>() { // 1. AccessController,Java安全模型
        public Void run() {
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 2. 核心,ServiceLoader就是JDK提供的SPI的实现方式
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next(); // 3. 遍历的过程会触发每个Driver实现类的加载
                }
            } catch(Throwable t) {
              // Do nothing
            }
            return null;
        }
    });
}

  ServiceLoader#load方法中,通过获取Thread.currentThread()context class loader来根据配置加载对应的类。

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

    // Thread.currentThread().getContextClassLoader() 默认是系统类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

  Thread.currentThread().getContextClassLoader()默认返回应用类加载器,这个可以从sun.misc.Launch类中看到。

public class Launcher {

public Launcher() {
    ...
    try {
                               //应用类加载器
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    Thread.currentThread().setContextClassLoader(this.loader);
    ...
  }


}

总结

  分析了两种打破Java双亲委派的情况,希望大家看完总结消化一下。

标签:委派,场景,Java,Tomcat,ClassLoader,双亲,加载
来源: https://www.cnblogs.com/reecelin/p/16197167.html