线程上下文加载器
线程上下文类加载器(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中获得连接,只能通过线程上下文加载器来区分。