Java中的Class.forName与ClassLoader

314 阅读3分钟

开始

在 Java 中,Class.forName() 和 ClassLoader 用的其实并不是很多,仔细回忆这两个东西,您是在什么时候才用过它们呢???其实随着您的回忆当初在学习 Java 连接数据库的时候你用到 Class.forName() ,而 ClassLoader 应该是在我们熟悉的 Spring 框架中的 IOC 的实现用的较多了。

稍作分析

在 Java 中 Class.forName() 和 ClassLoader 可以"粗浅"的理解他们都是对类进行加载。ClassLoader 可以实现的功能是通过一个类的全限定名来获取描述此类的二进制字节流,获取到二进制流后放到JVM中。Class.forName() 方法实际上也是调用的ClassLoader 来实现的,其实我们通过 Class.forName() 的源码就可以看到:

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

最后的 return 中调用了一个 forName0 方法,方法的第二个参数默认是 true,通过阅读源码得知这个参数代表是否对加载的类进行初始化,设置为 true 时会类进行初始化,代表会执行类中的静态代码块,以及对静态变量的赋值等操作。

其实在 Class.java 类中还有另外一个 forName(String name, boolean initialize, ClassLoader loader) 的静态方法:

@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    Class<?> caller = null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // Reflective call to get caller class is only needed if a security manager
        // is present.  Avoid the overhead of making this call otherwise.
        caller = Reflection.getCallerClass();
        if (sun.misc.VM.isSystemDomainLoader(loader)) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}

很明显,这个方法属于 forName() 的重载,仔细看你会发现它最后也是 return 了一个 forName0,很明显这个 Class.forName() 完全可以对第二个参数 boolean initialize 进行自我控制,可以进行手动选择在加载类的时候是否要对类进行初始化。

举例

举个案例说一下吧,定义一个含有静态代码块,静态变量,静态方法(可以赋值给静态变量)的类:

package com.wlee.test;

public class DemoClass {
    static {
        System.out.println("执行了 - 静态代码块");
    }

    private static String filed = method();

    public static String method() {
        System.out.println("执行了 - 静态方法");
		System.out.println("执行了 - 静态变量赋值");
        return "执行了 - 静态变量赋值";
    }
}

使用 Class.forName() 进行测试:

package com.wlee.test;

import org.junit.jupiter.api.Test;

public class DemoTest {

    @Test
    public void test() throws ClassNotFoundException {//Class.forName进行测试
        Class.forName("com.wlee.test.DemoClass");
        System.out.println("------执行结束------");
    }
}

运行结果:

执行了 - 静态代码块
执行了 - 静态方法
执行了 - 静态变量赋值
------执行结束------

使用 ClassLoader 进行测试:

package com.wlee.test;

import org.junit.jupiter.api.Test;

public class DemoTest {

    @Test
    public void test() throws ClassNotFoundException {//Class.forName进行测试
        Class.forName("com.wlee.test.DemoClass");
        System.out.println("------执行结束------");
    }

    @Test
    public void test2() throws ClassNotFoundException {//ClassLoader进行测试
        ClassLoader.getSystemClassLoader().loadClass("com.wlee.test.DemoClass");
        System.out.println("------执行结束------");
    }
}

运行 test2() 的结果:

------执行结束------

很明显,Class.forName() 默认的在加载类的时候将类进了初始化,而 ClassLoader 的 loadClass 并没有对类进行初始化,只是把类加载到了虚拟机 JVM 中了。

应用场景

看到上面的举例之后,想想 Class.forName() 的应用,就是在 JDBC 中使用,通过使用 Class.forName() 方法来加载数据库连接驱动。其实很好理解,我们利用 JDBC 如果去连接数据库???第一步通过 Class.forName() 加载数据库驱动,通过我们分析知道 Class.forName() 会自动初始化类,因为 JDBC 中明确规定要求 Driver(数据库驱动)类必须向 DriverManager 注册自己,通过观看数据库连接驱动类的源码就可以知道它为什么要用 Class.forName() 了:

以MySQL驱动为例源码片段如下:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

我们看到 Driver 注册到 DriverManager 中的操作写在了静态代码块中,这就是为什么在写 JDBC 时使用 Class.forName() 的原因了。至于 ClassLoader 我们就不做太细致的研究了,如果你有兴趣请移步Java中的ClassLoader