亲宝软件园·资讯

展开

Java中线程上下文类加载器超详细讲解使用

Brycen Liu 人气:0

一、什么是线程上下文类加载器

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

如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。

Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源。

1.1、重要性

它可以打破双亲委托机制,父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的classLoader来加载类,这就可以改变父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型

1.2、使用场景

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

二、ServiceLoader简单介绍

它是一个简单的加载服务提供者的机制。通常服务提供者会实现服务当中所定义的接口。服务提供者可以以一种扩展的jar包的形式安装到java平台上扩展目录中,也可以添加到应用的classpath中。

问题分析:

服务的接口通常是由启动类加载器去加载的,那么它又是怎么去访问到我们放在应用classpath下的扩展服务提供者的呢?

其内部是通过扫描提供者配置文件,通过线程上下文类加载器来加载具体的实现类,线程上线文毋庸置疑默认就是我们的系统类加载器,这样就可以访问到我们具体的服务提供者了。

三、案例

3.1、使用ServiceLoader加载mysql驱动

package com.brycen.classloader;
import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;
public class MyTest26 {
    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = loader.iterator();
        while (iterator.hasNext()){
            Driver dirver = iterator.next();
            System.out.println(dirver.getClass()+", 类加载器:"+dirver.getClass().getClassLoader());
        }
        System.out.println("当前线程上线文类加载器:"+Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader类加载器:"+loader.getClass().getClassLoader());
    }
}

运行结果:

Driver接口的两个实现类是由系统类加载器加载的,而我们的ServiceLoader类加载又是启动类加载,此时正是因为使用线程类加载器中的系统类加载器。如果在加载之前,我们修改线程上线文类加载器为扩展类加载器时,那我们的两个实现类就加载不了了。

class com.mysql.jdbc.Driver, 类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
class com.mysql.fabric.jdbc.FabricMySQLDriver, 类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
当前线程上线文类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader类加载器:null

3.2、Class.forName加载Mysql驱动

public class MyTest27 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    	//加载并初始化com.mysql.jdbc.Driver
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");
    }
}

3.2.1、com.mysql.jdbc.Driver

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
	//静态代码块,初始化的时候会执行
    static {
        try {
        	//主动使用DriverManager,则该类也会初始化
        	//初始化完成后就调用DriverManager的registerDriver方法将自身添加到驱动集合中。
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

3.2.2、java.sql.DriverManager初始化

由于上面主动使用了DriverManager,那么该类也会初始化

public class DriverManager {
    // 注册JDBC驱动的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    ...
	...
    static {
    	//当初始化的时候会执行该方法
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    ...
    ...
    private static void loadInitialDrivers() {
        String drivers;
        //通过获取系统参数来加载jdbc的驱动,如果没有该参数则返回null
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
				//通过ServiceLoader来加载驱动,ServiceLoader已经在上面讲解过了
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                	//这里会将加载到的驱动保存到上面的registeredDrivers集合中去
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        println("DriverManager.initialize: jdbc.drivers = " + drivers);
        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }
    ...
    ...

3.2.3、调用DriverManager的registerDriver方法

当我们的DriverManager初始化完成之后,com.mysql.jdbc.Driver中的静态代码块就会执行registerDriver方法,然后将自身注册到registeredDrivers集合中去,这样就完成了注册驱动了

注:显而易见,从DriverManager中的loadInitialDrivers我们可以得知,我们及时不使用Class.forName(“com.mysql.jdbc.Driver”),mysql的驱动也能被加载,这是因为后期jdk使用了ServiceLoader

...
...
//这个方法在com.mysql.jdbc.Driver初始化的时候被调用
public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {
	//将驱动注册到registeredDrivers集合中去
    registerDriver(driver, null);
}
...
...

3.2.4、执行DriverManager.getConnection方法

@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();
	//封装用户名和密码
    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }
	//调用getConnection,并把基本信息和调用者的class(这里就是我们的MyTest27.class)
	//Reflection.getCallerClass()是个本地方法,返回调用者的class
    return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //这里获取调用者的类加载器,如果为null则获取线程上下文类加载
    //从而实现能够在DirverManager中访问到放在我们classpath目录下的驱动
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }
    println("DriverManager.getConnection(\"" + url + "\")");
    SQLException reason = null;
    for(DriverInfo aDriver : registeredDrivers) {
        //判断每一个驱动是否有权限,这里的权限就是判断该驱动的类加载器
        //和上面获取到的类加载器是否一致
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }
    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }
    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

加载全部内容

相关教程
猜你喜欢
用户评论