首页 > 其他分享 >jvm之线程上下文加载器与SPI

jvm之线程上下文加载器与SPI

时间:2023-04-23 10:35:19浏览次数:34  
标签:service Driver SPI 线程 jvm null Class 加载


线程上下文加载器

线程上下文类加载器(Thread Context Class Loader,简称TCCL)是从JDK1.2开始引入的。类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。

如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器,默认为系统类加载器,这点可以从下面的JDK中的源码中得到验证。

以下代码摘自sun.misc.Launch的无参构造函数Launch():

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
...

SPI

Service Provider Interface:服务提供者接口,简称SPI,是Java提供的一套用来被第三方实现或者扩展的API。常见的SPI有JDBC、JNDI、JAXP等,这些SPI的接口由Java核心库实现,而这些SPI的具体实现由第三方jar包实现。

下面先看一段经典的JDBC获取连接的代码:

// Class.forName("com.mysql.jdbc.Driver").newInstance();
Connection conn = java.sql.DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

我们可以Class.forName这一行被注释掉了,但依然可以正常运行,这是为什么呢?

下面通过跟踪源码一步一步分析原因:

先看DriverManager这个类,调用该类的静态方法getConnection(),就会导致该类的初始化,也就是会执行这个类的静态代码块,DriverManager的静态代码块如下:

// 静态代码块
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    // 加载驱动
    private static void loadInitialDrivers() {
        ...

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                // 读取 META-INF/services
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        // next()的时候才会去加载
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        ... 
    }

再来看一下ServiceLoader.load方法:

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

从load方法中可以看出取出了线程上下文加载器也就是系统类加载器传递给了后面的代码。

一路跟踪下去会发现系统类加载这个参数传给了里面的一个内部类LazyIterator:

private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader; // 系统类加载器
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

LazyIterator实现了Iterator接口,后面loadInitialDrivers中获取这个迭代器进行遍历,最终会调用下面的两个方法

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 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;
                }
                // 解析META-INF/services/java.sql.Driver文件
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 使用线程上下文加载器加载META-INF/services/java.sql.Driver中指定的驱动类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

再来看一下mysql-connector-java.jar包下META-INF/services/java.sql.Driver中内容:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

最后看一下com.mysql.jdbc.Driver的静态代码块:

static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

registerDriver方法将driver实例注册到JDk的java.sql.DriverManager类中,其实就是add到它的一个类型为CopyOnWriteArrayList,名为registeredDrivers的静态属性中,到此驱动注册基本完成,

总结:如果我们使用JDBC时没有主动使用Class.forName加载mysql的驱动时,那么JDBC会使用SPI机制去查找所有的jar下面的META-INF/services/java.sql.Driver文件,使用Class.forName反射加载其中指定的驱动类。

DriverManager类和ServiceLoader类都是属于rt.jar的,它们的类加载器是根类加载器。而具体的数据库驱动,却属于业务代码,这个根类加载器是无法加载的。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

使用TCCL校验实例的归属

下面再来看一下java.sql.DriverManager.getConnection()这个方法,这里面有个小细节:

//  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {

        // callerCL是调用这个方法的类所对应的类加载器
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
        ...
        // 遍历注册到registeredDrivers里的Driver类
        for(DriverInfo aDriver : registeredDrivers) {
            // 使用线程上下文类加载器检查Driver类有效性,重点在isDriverAllowed中,方法内容在后面
            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());
            }

        }
        ...
    }

    private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                // 传入的classLoader为调用getConnetction的线程上下文类加载器,使用这个类加载器再次加载驱动类
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }
            // 这里只有aClass和driver.getClass()是由同一个类加载器加载才会相等
             result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

isDriverAllowed这个方法的意义:例如在tomcat中,多个webapp都有自己的Classloader,如果它们都自带mysql-connect.jar包,那底层Classloader的DriverManager里将注册多个不同类加载器加载的Driver实例,webapp想要从DriverManager中获得连接,只有通过线程上下文加载器区分了。

更多精彩内容关注本人公众号:架构师升级之路

jvm之线程上下文加载器与SPI_jvm


标签:service,Driver,SPI,线程,jvm,null,Class,加载
From: https://blog.51cto.com/u_6784072/6216466

相关文章

  • jvm如何打破双亲委托机制
    打破双亲委托机制重写父类ClassLoader的loadClass方法packagecom.morris.jvm.classloader;publicclassBreakDelegateClassLoaderextendsMyClassLoader{@OverrideprotectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{......
  • JVM内存模型
    JVM内存模型JVM的内存模型也就是JVM中的内存布局,不要与java的内存模型(与多线程相关)混淆。下图是jdk8jvm内存模型图:程序计数器程序计数器是当前线程所执行的字节码的行号指示器。JVM支持多个线程同时运行,每个线程都会根据CPU时间片来回切换,那么如果当前线程获得时间片了,怎么知道它......
  • Java虚拟机之JVM工具监控调优
    我是攻城师(woshigcs)前几篇我们学习了,JVM里面的运行结构,GC算法,以及各种垃圾收集器的优劣点,那么本篇我们来看下如何使用一些虚拟机性能监控工具,来监控和快速处理故障,当JVM出现一些故障时,我们通常从如下的几个方面进行着手分析,包括运行日志,异常堆栈,GC日志,线程快照(threaddump/javacor......
  • 线程使用方式
    有三种使用线程的方法:实现Runnable接口;实现Callable接口;继承Thread类。实现Runnable和Callable接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过Thread来调用。可以说任务是通过线程驱动从而执行的。实现Runnable接口......
  • SPI机制的简单示例?
    我们现在需要使用一个内容搜索接口,搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索。    可以看到输出结果:文件搜索helloworld如果在com.cainiao.ys.spi.learn.Search文件里写上两个实现类,那最后的输出结果就是两行了。这就是因为ServiceLoader.lo......
  • 线程状态转换
    新建(New)创建后尚未启动。可运行(Runnable)可能正在运行,也可能正在等待CPU时间片。包含了操作系统线程状态中的Running和Ready。阻塞(Blocking)等待获取一个排它锁,如果其线程释放了锁就会结束此状态。无限期等待(Waiting)等待其它线程显式地唤醒,否则不会被分配CPU......
  • 什么是SPI机制?
    SPI(ServiceProviderInterface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服......
  • JDK,JRE,JVM之间的关系
    JDK,JRE,JVM三者之间的关系JDK=JRE+开发工具集(例如javac编译工具等)JRE=JVM+JavaSE标准类库JDK(Java开发工具包)JRE(Java运行环境)JVM(Java虚拟机)......
  • ZSTD多线程压缩
    测试代码:1//main.cpp2//34#include<iostream>5#include<fstream>6#include<vector>7#include<chrono>8#include<thread>9#include"./zstd/lib/zstd.h"1011usingbyte=uint8_t;12usingbuff......
  • 为什么Sync.Pool不需要加锁却能保证线程安全
    1.简介我们在Sync.Pool:提高go语言程序性能的关键一步一文中,已经了解了使用sync.Pool来实现对象的复用以减少对象的频繁创建和销毁,以及使用sync.Pool的一些常见注意事项。在这篇文章中,我们将剖析sync.Pool内部实现中,介绍了sync.Pool比较巧妙的内部设计思路以及其实现方式。......