双亲委派模式破坏-JDBC

关于类加载机制推荐这篇:深入探讨Java类加载机制
关于双亲委派模式的破坏,如Tomcat的类加载机制
关于JAVA的SPI机制,Java-SPI

JDBC之所以要破坏双亲委派模式是因为,JDBC的核心在rt.jar中由启动类加载器加载,而其实现则在各厂商实现的的jar包中,根据类加载机制,若A类调用B类,则B类由A类的加载器加载,也就是说启动类加载器要加载jar包下的类,我们都知道这是不可能的,启动类加载器负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,那么JDBC是如何加载这些Driver实现类的?
通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类。

先来看一个例子:

		
			mysql
			mysql-connector-java
			
			5.1.45
		
public static void main(String[] args)
    {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        Driver driver;
        while (drivers.hasMoreElements())
        {
            driver = drivers.nextElement();
            System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
        }
        System.out.println(DriverManager.class.getClassLoader());
    }

输出结果如下:

class com.mysql.jdbc.Driver-----sun.misc.Launcher$AppClassLoader@18b4aac2
class com.mysql.fabric.jdbc.FabricMySQLDriver-----sun.misc.Launcher$AppClassLoader@18b4aac2
DriverManager classLoader:null

可以看到代码中并没有调用 Class.forName(“”)的代码,但DriverManager中已经加载了两个 jdbc 驱动,而却这两个驱动都是使用的应用类加载器(AppClassLoader)加载的,而DriverManager本身的类加载器确是 null 即BootstrapClassLoader,按照双亲委派模型的规则,委派链如下:
SystemApp class loader -> Extension class loader -> Bootstrap class loader
,父加载器BootstrapClassLoader是无法找到AppClassLoader加载的类的

查看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;
            }
        });
        ......

关于AccessController的doPrivileged使用
调用了ServiceLoader.load得到ServiceLoader对象,该类是JAVA的SPI机制的实现,内部实现了一个迭代器,循环调用其next方法,该方法里调用Class.forName加载各厂商的Driver类,如com.mysql.jdbc.Driver。

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

这里获得线程上下文的ClassLoader(关于getContextClassLoader,解释在文章最后),最终将被Class.forName使用,用该加载器来加载在我们项目下的com.mysql.jdbc.Driver。

创建了ServiceLoader对象,将得到的ClassLoaderDriver.class传进去

public final class ServiceLoader<S>
    implements Iterable<S>
{
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

reload方法创建了一个LazyIterator,来看看它的hasNextnext方法

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

hasNext调用了hasNextService

private static final String PREFIX = "META-INF/services/";

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
//fullName = META-INF/services/java.sql.Driver
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

该方法就是获取META-INF/services/java.sql.Driver中下一个Driver的全限定名。以mysql-connector为例:
双亲委派模式破坏-JDBC_第1张图片在这里插入图片描述
在上面例子中输出结果就是这两个Driver。
next方法就是调用Class.forName通过hasNext里得到的nextName 与传过来的ClassLoader加载Driver类。

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

调用nextService

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);

			......
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);

加载并初始化了Driver类。

JDBC通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类。

关于getContextClassLoader:
线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()setContextClassLoader(ClassLoader c1),分别用来获取和设置类加载器。

如果没有通过setContextClassLoader方法进行设置的话,线程将继承其父线程的上下文加载器,java应用运行时的初始线程的上下文类加载器是系统类加载器(这里是由Launcher类设置的)。在线程中运行的代码可以通过该类加载器来加载类和资源。

SPI(Service Provider Interface,服务提供者接口,指的是JDK提供标准接口,具体实现由厂商决定。例如sql),如上面的JDBC

父ClassLoader可以使用当前线程Thread.current.currentThread().getContextClassLoader()所指定的classLoader加载的类。这就改变了父ClassLoader不能使用子ClassLoader加载的类的情况,即改变了双亲委托模型。

线程上下文类加载器就是当前线程的CurrentClassloader。

在双亲委托模型下,类加载器是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是JAVA核心库提供的,而JAVA核心库是由启动类加载器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供),JAVA的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以设置的上下文类加载器来实现对于接口实现类的加载

你可能感兴趣的:(JVM,Java进阶)