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

目录
  • 一、什么是线程上下文类加载器
    • 1.1、重要性
    • 1.2、使用场景
  • 二、ServiceLoader简单介绍
  • 三、案例
    • 3.1、使用ServiceLoader加载mysql驱动
    • 3.2、Class.forName加载Mysql驱动
      • 3.2.1、com.mysql.jdbc.Driver
      • 3.2.2、java.sql.DriverManager初始化
      • 3.2.3、调用DriverManager的registerDriver方法
      • 3.2.4、执行DriverManager.getConnection方法

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

线程上下文类加载器(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中。

  • 服务提供者需要提供一个无参数的构造方法
  • 服务提供者是通过在META-INF/services目录下相应的提供者配置文件,该配置文件的文件名由服务接口的包名组成。
  • 提供者配置文件里面就是实现这个服务接口的类路径,每个服务提供者占一行。
  • ServiceLoader是按需加载和实例化提供者的,就是懒加载,ServiceLoader其中还包含一个服务提供者缓存,里面存放着已经加载的服务提供者。
  • ServiceLoader会返回一个iterator迭代器,会返回所有已经加载了的服务提供者。
  • ServiceLoader是线程不安全的

问题分析:

服务的接口通常是由启动类加载器去加载的,那么它又是怎么去访问到我们放在应用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线程上下文类加载器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java通过自定义类加载器实现类隔离

    目录 前言 类隔离是什么 使用场景 解决方案 重写findClass 重写loadClass 总结 前言 由于微服务的快速迭代.持续集成等特性,越来越多的团队更倾向于它.但是也体现出了一些问题,比如在基础设施建设过程中,需要把通用功能下沉,把现有大而全的基础设施按领域拆分,考虑需要兼容现有生产服务,会产生不同的依赖版本,有时不注意就可以引发问题.比如本文遇到的依赖包版本冲突问题,以及如何利用类隔离技术解决的分析. 类隔离是什么 类隔离是一种通过类加载器实现加载所需类的实现方式,使得不同版本类间隔

  • Java类加载器ClassLoader源码层面分析讲解

    目录 Launcher 源码 AppClassLoader 源码 ExtClassLoader 源码 ClassLoader 源码 总结 最终总结一下 Launcher 源码 sun.misc.Launcher类是java 虚拟机的入口,在启动 java应用 的时候会首先创建Launcher.在初始化Launcher对象的时候会创建一个ExtClassLoader拓展程序加载器 和 AppClassLoader应用程序类加载器(这俩鬼东西好像只是加载类的路径不一样而已),然后由这俩类加载器去加载

  • Java中如何自定义一个类加载器

    目录 如何自定义加载器? 示例:读取某文件的下的某class文件 类加载器的使用及自定义类加载器 如何自定义加载器? 1.创建一个自定义加载器类 继承 ClassLoader 类 2.重写 findClass 方法. 主要是实现从那个路径读取 jar包或者.class文件,将读取到的文件用字节数组来存储,然后可以使用父类的 defineClass 来转换成字节码. 如果想破坏双亲委派的话,就重写 loadClass 方法, 否则不用重写 注意: 1.ClassLoader提供的 protecte

  • Java类加载器ClassLoader的使用详解

    目录 BootstrapClassLoader ExtClassLoader AppClassLoader 类加载器的具体实现在哪里 类加载器的初始化时机 如何进行的类加载 Loader.getResource(resourceName) 调用获取资源方法的一些常见问题 BootstrapClassLoader 加载范围(根据系统参数): System.getProperty("sun.boot.class.path"); 负责加载核心类库,以我的本地的环境来展示获取的内容: D:\d

  • java自定义类加载器如何实现类隔离

    目录 自定义类加载器 准备 通过URLClassLoader来实现[推荐] 通过继承ClassLoader实现 网上java自定义类加载器很多容易找到,但是都是加载的单个类,如果被加载的类,有引用了其他类怎么办呢?接下来看一下如何来处理这种情况 有时候一个项目中可能会引用不同版本的第三方依赖,比如笔者在升级hbase系统时,代理层就同时用到了1.X和2.X版本的hbase-client的jar包. 当时是使用的阿里的SOFAArk来实现的. 它的本质就是是哟个类加载来实现的,接下来就通过一个小例

  • Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    目录 一.类加载器 1.启动类加载器 2.拓展类加载器 3.应用类加载器 4.类的命名空间 二.双亲委派机制 1.类加载机制流程 2.类加载器加载顺序 3.双亲委派机制流程 4.源码分析 5.双亲委派机制优缺点 三.线程上下文类加载器 1.线程上下文类加载器(Context Classloader) 2.ServiceLoader 四.自定义类加载器 一.类加载器 类加载器就是根据类的二进制名(binary name)读取java编译器编译好的字节码文件(.class文件),并且转化生成一个ja

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

    目录 一.什么是线程上下文类加载器 1.1.重要性 1.2.使用场景 二.ServiceLoader简单介绍 三.案例 3.1.使用ServiceLoader加载mysql驱动 3.2.Class.forName加载Mysql驱动 3.2.1.com.mysql.jdbc.Driver 3.2.2.java.sql.DriverManager初始化 3.2.3.调用DriverManager的registerDriver方法 3.2.4.执行DriverManager.getConnection

  • Java中JDK动态代理的超详细讲解

    目录 1. 什么是动态代理? 2.动态代理的实现方式有几种? 3. JDK动态代理 4. CGLB动态代理 5.动态代理的效率 6.为什么要使用动态代理呢? 7. JDK动态代理详细使用介绍 总结 1. 什么是动态代理? 动态代理是通过创建代理对象,在不改变原有代码的基础上,给程序增加新的功能,实现了程序的功能增强. 2.动态代理的实现方式有几种? JDK动态代理 CGLB动态代理 3. JDK动态代理 使用了JDK中的InvocationHandler接口,Method类和Proxy类.JDK

  • 一文带你弄懂Java中线程池的原理

    目录 为什么要用线程池 线程池的原理 ThreadPoolExecutor提供的构造方法 ThreadPoolExecutor的策略 线程池主要的任务处理流程 ThreadPoolExecutor如何做到线程复用的 四种常见的线程池 newCachedThreadPool newFixedThreadPool newSingleThreadExecutor newScheduledThreadPool 小结 在工作中,我们经常使用线程池,但是你真的了解线程池的原理吗?同时,线程池工作原理和底层实

  • Java超详细讲解多线程中的Process与Thread

    目录 进程和线程的关系 操作系统是如何管理进程的 并行和并发 创建线程的方法 串行执行和并发执行 Thread中的一次额重要方法 中断线程 线程等待 线程休眠(sleep) 进程和线程的关系 在操作系统中运行的程序就是进程,比如说QQ,播放器,游戏等等…程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程和线程都是为了处理并发编程这样的场景,但是进程有问题,频繁拆功创建和释放资源的时候效率低,相比之下,线程更轻量,创建和释放效率更高. 进程具有独立性,每个进程有各自独立

  • Java语言中的自定义类加载器实例解析

    本文研究的主要是Java语言中的自定义类加载器实例解析的相关内容,具体如下. 自己写的类加载器 需要注意的是:如果想要对这个实例进行测试的话,首先需要在c盘建立一个c://myjava的目录.然后将相应的java文件放在这个目录中.并将产生的.clas文件放在c://myjava/com/lg.test目录下,否则是找不到的.这是要注意的.. class FileClassLoader : package com.lg.test; import java.io.ByteArrayOutputSt

  • 超详细讲解Java线程池

    目录 池化技术 池化思想介绍 池化技术的应用 如何设计一个线程池 Java线程池解析 ThreadPoolExecutor使用介绍 内置线程池使用 ThreadPoolExecutor解析 整体设计 线程池生命周期 任务管理解析 woker对象 Java线程池实践建议 不建议使用Exectuors 线程池大小设置 线程池监控 带着问题阅读 1.什么是池化,池化能带来什么好处 2.如何设计一个资源池 3.Java的线程池如何使用,Java提供了哪些内置线程池 4.线程池使用有哪些注意事项 池化技术

  • Java 超详细讲解SpringMVC拦截器

    目录 拦截器(interceptor)的作用 拦截器和过滤器区别 拦截器快速入门 多拦截器操作 拦截器方法说明 本章小结 拦截器(interceptor)的作用 Spring MVC 的 拦截器 类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行 预处理 和 后处理 . 将拦截器按一定的顺序联结成一条链,这条链称为 拦截器链(Interceptor Chain) .在访问被拦截的方 法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用.拦截器也是AOP思想的具体实现.

  • Java超详细讲解WebMvcConfigurer拦截器

    目录 addInterceptors拦截器 addViewControllers页面跳转 addResourceHandlers静态资源 configureViewResolvers视图解析器 addCorsMappings跨域 configureMessageConverters信息转换器 WebMvcConfigurer接口常用的方法: /* 拦截器配置 */ void addInterceptors(InterceptorRegistry var1); /* 视图跳转控制器 */ void

  • Java超详细讲解设计模式中的命令模式

    目录 介绍 实现 个人理解:把一个类里的多个命令分离出来,每个类里放一个命令,实现解耦合,一个类只对应一个功能,在使用命令时由另一个类来统一管理所有命令. 缺点:如果功能多了就会导致创建的类的数量过多 命令模式(Command Pattern)是⼀种数据驱动的设计模式,它属于行为型模式.请求以命令的形式包裹在对象中,并传给调⽤对象.调⽤对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执⾏命令. 介绍 意图:将⼀个请求封装成⼀个对象,从⽽使您可以⽤不同的请求对客户进⾏参数化.

随机推荐