1、class.forName()源码:
以JDBC驱动加载为例:
1)通过Reflection.getCallerClass()获取调用该方法的类
2)获取调用者的类加载器,并使用调用者的类加载器去加载JDBC驱动,并初始化驱动类,在初始化驱动类之前,先初始化DriverManager。
3)初始化DriverManager,执行静态块,遍历META-INF/services目录下描述的所有驱动类,使用AppClassLoader初始化这些驱动类
4)将初始化后的驱动类实例注册到DriverManager上,通过URL、name/pwd等信息获取连接。这也是我们可用通过DriverManager获取连接的原因。
``
一、class.forName方法源码:
public static Class<?> forName(String className)
throws ClassNotFoundException {
//这个方法获取调用该方法的类,是为了获得该类的类加载器,来加载驱动
Class<?> caller = Reflection.getCallerClass();
//forName0是一个本地方法,有4个参数
//className表示要加载器的类的全路径名
//initialize 布尔值,true表示加载并初始化该类,false表示加载不初始化该类
//loader:表示使用哪个类加载器,本利使用调用者的类加载器
//caller:表示调用forname方法的类
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
根据这一段代码可以知道Class.forname()会初始化要加载的类;类的加载器分3个阶段:
加载、连接(校验、准备(赋初值)、解析(替换引用)、初始化);而类加载器的loadClass()方法并不一定会初始化加载的类。
二、初始化com.mysql.jdbc.Driver这个类
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
代码很简单,如果Driver没有被初始化过,就会执行sttaic块,就是new一个Driver()实例,然后注册给DriverManager。
三、DriverManager.getConnection()调用,属于静态方法调用,会初始化DriverManager,其静态块如下:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
初始化方法loadInitialDrivers()的代码如下:
private static void loadInitialDrivers() {
String drivers;
try {
// 从系统属性中读取"jdbc.drivers"的值,jdbc.drivers表示要驱动,可以有多个
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// 这里通过上下类类加载器去加载驱动实现类jar目录META-INF/services/目录下定义
//的所有驱动类。
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//ServiceLoader去加载META-INF/services/目录下的所有驱动类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
// 使用AppClassloader加载
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
上面的loadInitialDrivers方法就是将META-INF/services目录下的java.sql.Driver文件中定义的驱动全部加载并初始化。META-INF/services目录在各个厂商提供的驱动jar包中。
这里可以看出,即使去掉class.firname()也不影响程序,因为这里已经遍历了所有的驱动并初始化了。
四、调用Manager.getConnection()返回连接。主要代码是一个for循环,初始化所有注册在Manager中的驱动类。
for(DriverInfo aDriver : registeredDrivers) {// registeredDrivers包含所有注册在里面的驱动类
//重点看这个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());
}
}
isDriverAllowed方法:
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
//这里使用AppClassLoader再一次初始化传入的Driver
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
//注意:这里只有同一个类加载器加载的相同全限定名的类相等,这里主要验证加载该驱动的
//类加载器是否一致,这里是命名空间在起作用。如果不同的类加载器,可能会引起相互不可
//见的问题。这也常用于tomcat服务区来隔离不同的web应用。
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
2、Tomcat中的类加载器
在Tomcat目录结构中,有三组目录(“/common/”,“/server/”和“shared/”)可以存放公用Java类库,此外还有第四组Web应用程序自身的目录“/WEB-INF/,把java类库放置在这些目录中的含义分别是:
放置在common目录中:类库可被Tomcat和所有的Web应用程序共同使用。
放置在server目录中:类库可被Tomcat使用,但对所有的Web应用程序都不可见。
放置在shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。
灰色背景的3个类加载器是JDK默认提供的类加载器,这3个加载器的作用前面已经介绍过了。而 CommonClassLoader、CatalinaClassLoader、SharedClassLoader 和 WebAppClassLoader 则是 Tomcat 自己定义的类加载器,它们分别加载 /common/、/server/、/shared/ 和 /WebApp/WEB-INF/* 中的 Java 类库。其中 WebApp 类加载器和 Jsp 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 Jsp 类加载器。
从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。
3、Spring加载问题
Tomcat 加载器的实现清晰易懂,并且采用了官方推荐的“正统”的使用类加载器的方式。对于Spring有一个问题:如果有 10 个 Web 应用程序都用到了spring的话,可以把Spring的jar包放到 common 或 shared 目录下让这些程序共享。Spring 的作用是管理每个web应用程序的bean,getBean时自然要能访问到应用程序的类,而用户的程序显然是放在 /WebApp/WEB-INF 目录中的(由 WebAppClassLoader 加载),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的Class呢?
答案:通过上下文类加载器去加载包括common和shared目录下的class。spring中的上下文类加载器就是WebAppClassLoader