文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2022-12-22 18:00

关注

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

线程上下文类加载器(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线程上下文类加载器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯