当前位置: 首页 > 图灵资讯 > 技术篇> jvm之线程上下文加载器与SPI

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

来源:图灵教育
时间:2023-04-24 10:22:00

线程上下文加载器

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

如果没有setcontexclasloder,(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这一类,调用这种静态方法获取Conection(),就会导致这一类的初始化,即将执行这一类的静态代码块,DriverMananager的静态代码块如下:

// 静态代码块    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;            }        });        ...     }

让我们来看看Serviceloder.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.Drivercom.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,它的静态属性叫registerdrivers,到此驱动注册基本完成,

总结:如果使用JDBC时不主动使用Classs.当forname加载mysql驱动时,JDBC将使用SPI机制在jar下找到所有META-INF/services/java.sql.使用Classss的Driver文件.forname反射加载指定的驱动类。

drivermanager类和serviceloder类都属于rtiver.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 !> 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();            }        }        ...        // Driver类别在registerdrivers中注册        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 {                // 为了调用getconetction的线程上下文类加载器,传入clasloader使用此类加载器再次加载驱动类型                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都有自己的classloder,如果都有自己的mysql-connect.Jar包,底层Clasloader的DriverManager将注册多个不同类型加载器加载的Driver实例,webapp想要从DriverManager中获得连接,只能通过线程上下文加载器来区分。