java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java线程上下文类加载器

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

作者:Brycen Liu

这篇文章主要介绍了Java中线程上下文类加载器,类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例的代码模块。本文主要和大家聊聊JVM类加载器ClassLoader的使用,需要的可以了解一下

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

线程上下文类加载器(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");
}

到此这篇关于Java中线程上下文类加载器超详细讲解使用的文章就介绍到这了,更多相关Java线程上下文类加载器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文