您的位置:首页 > 编程语言 > Java开发

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

2017-08-31 16:48 489 查看
参考文档:

参加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
的值为 
true
loader
的值为当前类的类加载器。
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来加载了,完全破坏了双亲委派模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐