每日一则JavaWeb---ClassLoader原理解析

参考文档:

参加class对象详解

java中的class对象

深入解析类加载器

深入探讨 Java 类加载器

真正理解线程上下文类加载器

对于类的加载机制的探讨主要是最近的Spring源码的第一的解读过程中总是会出现一些很有意思的东西如下:

ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}
同时最近看到JDBC的接口是在核心类加载器加载的,而实现确实在App类加载器加载的,很相似的一点,都使用了一个连接的容器去连接,在JDBC中使用了CopyOnWriteArrayList而在Spring中使用了ConcurrentHashMap来连接,异曲同工的打破了,双亲委托这种机制的关系。。。。


案例如下:

以mysql为例,介绍一下驱动注册及获取connection的过程:

// 注册驱动类
Class.forName("com.mysql.jdbc.Driver").getInstance(); 
String url = "jdbc:mysql://localhost:3306/testdb";    
// 通过java库获取数据库连接
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password"); 
这里就需要了解的是Class.forName的用法了:

Class.forName是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。第二种形式则相当于设置了参数 initialize的值为 trueloader的值为当前类的类加载器。Class.forName的一个很常见的用法是在加载数据库驱动的时候。

Class.forName()加载com.mysql.jdbc.Driver类,注意该类是java.sql.Driver接口的实现(class Driver extends NonRegisteringDriver implements java.sql.Driver),它们名字相同,在下面的描述中将带上package名避免混淆。 
它将运行其static静态代码块:

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}
实实在在的来了,沟通上下游的问题

registerDriver方法将本类(new com.mysql.jdbc.Driver())注册到系统的DriverManager中,其实就是add到它的成员常量CopyOnWriteArrayList registeredDrivers中。

好,接下来的java.sql.DriverManager.getConnection()才算是进入了正戏。它最终调用了以下方法:

private static Connection getConnection(
     String url, java.util.Properties info, Class<?> caller) throws SQLException {
     /* 传入的caller由Reflection.getCallerClass()得到,该方法 * 可获取到调用本方法的Class类,这儿调用者是java.sql.DriverManager(位于/lib/rt.jar中), * 也就是说caller.getClassLoader()本应得到Bootstrap启动类加载器 * 但是在上一篇文章中讲到过启动类加载器无法被程序获取,所以只会得到null * 这时问题来了,DriverManager是启动类加载器加载的,可偏偏又要在这儿加载子类的Class * 子类是通过jar包的方式放入classpath中的,由AppClassLoader加载 * 因此这儿通过双亲委派方式肯定无法加载成功,因此这儿借助 * ContextClassLoader来加载mysql驱动类(简直作弊啊!) * 上一篇文章最后也讲到了Thread.currentThread().getContextClassLoader() * 默认set了AppClassLoader,也就是说把类加载器放到Thread里,那么执行方法时任何地方都可以获取到它。 */
     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");
     }

     SQLException reason = null;
     // 遍历刚才放到registeredDrivers里的Driver类
     for(DriverInfo aDriver : registeredDrivers) {
         // 检查能否加载Driver类,如果你没有修改ContextClassLoader,那么默认的AppClassLoader肯定可以加载
         if(isDriverAllowed(aDriver.driver, callerCL)) {
             try {
                 println(" trying " + aDriver.driver.getClass().getName());
                 // 调用com.mysql.jdbc.Driver.connect方法获取连接
                 Connection con = aDriver.driver.connect(url, info);
                 if (con != null) {
                     // Success!
                     return (con);
                 }
             } catch (SQLException ex) {
                 if (reason == null) {
                     reason = ex;
                 }
             }

         } else {
             println(" skipping: " + aDriver.getClass().getName());
         }

     }
     throw new SQLException("No suitable driver found for "+ url, "08001");
 }

这里面就有了这个关键词:获取了当前线程的ClassLoader,

Class<?> callerClass = Reflection.getCallerClass();
其中线程上下文类加载器的作用已经在上面的注解中详细说明了,获取的Class也就是咱们刚刚看到的Class.forName获取的,其中用 connect() 方法获取连接,数据库厂商必须实现该方法,然而调用时DriverManager来加载外部实现类并调用 com.mysql.jdbc.Driver.connect() 来获取connection,所以这儿只能拜托Thread中保存的AppClassLoader来加载了,完全破坏了双亲委派模式。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值