注:这个问题,其实到网上一搜就一大把相关的说明文章,讲的也差不多。那为什么笔者还要花时间整理呢?首先是作为自己知识体系的一个整理和总结,其次是想大致说明白写的这些东西到底是哪来的,然后尽可能多地贴出相关的来源链接(其实大部分就是官方权威说明文档)供大家参考和学习,还原出这个推断的过程,最后想表达的还是同样一个意思:接口说明尽可能看官方文档或者源码,可以说是基本解释得七七八八。总之,最重要的是自己思考和求证、总结的过程,获得收获的是自己。
本篇目录结构如下:
1、Class.forName和ClassLoader的区别
回归正文,笔者认为,这个问题应该得再说具体一点,譬如: Class.forName(String className) 方法和 ClassLoader.loadClass(String name) 的区别。因为可以实现与之对等功能的方法有 Class.forName(String name, boolean initialize, ClassLoader loader) 和 ClassLoader.loadClass(String name, boolean resolve)(loadClass(String name) 内部就是直接调用了 #loadClass(name, false) 进行处理),后面会对此进行分析。
这里直接先上结论,毕竟这是API接口功能的差异,很多人都已经知道结论了,也不是什么新鲜事:
- ClassLoader.loadClass(String name) 只会找对应的 class 字节码并加载(Loading)到JVM中,不会干其他的事,例如链接(Linking)、初始化(Initialization)等都不会再进一步处理。
- Class.forName(String className) 不仅会找对应的 class 字节码并加载(Loading)到JVM中,还会进行链接(Linking)、初始化(Initialization),执行类的静态代码块和静态变量的初始化。
示例(参考笔者 github):
package cn.wpbxin.javabasis.reflection; /** * 用于验证 ClassForName 和 ClassLoader 的实例类 * @author wpbxin * */ public class ClassForNameInstance { public static int i = 1; // 普通代码块 { System.out.println("instance block"); } // 静态代码块 static { System.out.println("static block executes:"); System.out.println("i=" + i); } // 静态方法 public static void staticMethod() { System.out.println("staticMethod invoke"); } } /// 测试类 package cn.wpbxin.javabasis.reflection; /** * <tt>Class.forName</tt> 和 <tt>ClassLoader</tt> 的区别比较 * @author wpbxin * */ public class ClassForNameAndClassLoader { /** * * @param args * @throws ClassNotFoundException */ public static void main(String[] args) throws ClassNotFoundException { System.out.println("+++++++++++++++++++++++++++++++++"); String name = "cn.wpbxin.javabasis.reflection.ClassForNameInstance"; System.out.println("ClassLoader.loadClass"); Class<?> loaderInstance = ClassLoader.getSystemClassLoader().loadClass(name); System.out.println("class name = " + loaderInstance.getName()); System.out.println("---------------------------------"); System.out.println("Class.forName"); Class<?> fornameInstance = Class.forName(name); System.out.println("class name = " + fornameInstance.getName()); } }
类加载机制
仔细观察,上面提到的其实就是大家比较熟悉的类加载过程:加载、链接、初始化,接着便是实例化和使用,然后就是实例对象的清理回收(内存回收),最后就是类的卸载。这里提到的其实就是前面的3个步骤: 加载、链接 和 初始化,ClassLoader.loadClass(String name) 只做了第一步,而 Class.forName(String className) 则都处理了。整个过程大概就是下面这张大家常见的图示(图片来源于网络)。
至于为何是这个过程,其实可以说是JVM的规范之一,具体可以参考下 [1]Chapter 12. Execution 中提到的Java程序应用的一整个完整的执行流程,或者也可以硬刚虚拟机规范《The Java® Virtual Machine Specification Java SE 8 Edition》中的相关说明 [5]Chapter 5. Loading, Linking, and Initializing 。这么一看的话,这个图其实就是对这个过程的一个总结。
具体的类加载过程说明和分析,除了参考上面给出的官方链接外,也强烈建议参考《深入理解Java虚拟机》第三版,笔者这里就不赘述了。
2、Class.forName和ClassLoader的差异分析
下面我们来看下具体的源码和API说明,理清差异的原因。
Class.forName(String className) 接口源码如下:
/** * Returns the {@code Class} object associated with the class or * interface with the given string name. Invoking this method is * equivalent to: * * <blockquote> * {@code Class.forName(className, true, currentLoader)} * </blockquote> * * where {@code currentLoader} denotes the defining class loader of * the current class. * * <p> For example, the following code fragment returns the * runtime {@code Class} descriptor for the class named * {@code java.lang.Thread}: * * <blockquote> * {@code Class t = Class.forName("java.lang.Thread")} * </blockquote> * <p> * A call to {@code forName("X")} causes the class named * {@code X} to be initialized. * * @param className the fully qualified name of the desired class. * @return the {@code Class} object for the class with the * specified name. * @exception LinkageError if the linkage fails * @exception ExceptionInInitializerError if the initialization provoked * by this method fails * @exception ClassNotFoundException if the class cannot be located */ @CallerSensitive public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }
其中的注释说明,我这里重新贴一下,方便看:
Returns the Class object associated with the class or interface with the given string name. Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of the current class.
For example, the following code fragment returns the runtime Class descriptor for the class named java.lang.Thread:
Class t = Class.forName("java.lang.Thread")
A call to forName("X") causes the class named X to be initialized.
从注释可以看出, Class.forName(String className) 的调用其实等价于 Class.forName(className, true, currentLoader) 。这两最终都是调用了本地方法 forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) ,这个调用通过将参数 initialize 设置为 true 来指定进行类的初始化,而类的初始化过程会调用静态代码块。既然会进行初始化,那肯定会在初始化之前进行加载和链接的。
Class.forName(String name, boolean initialize, ClassLoader loader) 接口源码如下,这里我们可以看到参数 initialize 用于控制是否进行初始化,false 表示只进行加载和链接,true表示还需要进行初始化,而参数 ClassLoader 则是用于进行类加载的。
/** * Returns the {@code Class} object associated with the class or * interface with the given string name, using the given class loader. * Given the fully qualified name for a class or interface (in the same * format returned by {@code getName}) this method attempts to * locate, load, and link the class or interface. The specified class * loader is used to load the class or interface. If the parameter * {@code loader} is null, the class is loaded through the bootstrap * class loader. The class is initialized only if the * {@code initialize} parameter is {@code true} and if it has * not been initialized earlier. * * <p> If {@code name} denotes a primitive type or void, an attempt * will be made to locate a user-defined class in the unnamed package whose * name is {@code name}. Therefore, this method cannot be used to * obtain any of the {@code Class} objects representing primitive * types or void. * * <p> If {@code name} denotes an array class, the component type of * the array class is loaded but not initialized. * * <p> For example, in an instance method the expression: * * <blockquote> * {@code Class.forName("Foo")} * </blockquote> * * is equivalent to: * * <blockquote> * {@code Class.forName("Foo", true, this.getClass().getClassLoader())} * </blockquote> * * Note that this method throws errors related to loading, linking or * initializing as specified in Sections 12.2, 12.3 and 12.4 of <em>The * Java Language Specification</em>. * Note that this method does not check whether the requested class * is accessible to its caller. * * <p> If the {@code loader} is {@code null}, and a security * manager is present, and the caller's class loader is not null, then this * method calls the security manager's {@code checkPermission} method * with a {@code RuntimePermission("getClassLoader")} permission to * ensure it's ok to access the bootstrap class loader. * * @param name fully qualified name of the desired class * @param initialize if {@code true} the class will be initialized. * See Section 12.4 of <em>The Java Language Specification</em>. * @param loader class loader from which the class must be loaded * @return class object representing the desired class * * @exception LinkageError if the linkage fails * @exception ExceptionInInitializerError if the initialization provoked * by this method fails * @exception ClassNotFoundException if the class cannot be located by * the specified class loader * * @see java.lang.Class#forName(String) * @see java.lang.ClassLoader * @since 1.2 */ @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); }
接下来我们来看下 ClassLoader 。
ClassLoader 则是大家常说的类加载器,它遵循双亲委派模型进行类的加载,最终会调用启动类加载器的类加载器,类加载器实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。ClassLoader.loadClass(String name) 中的参数 name 是官方文档提到的 binary name ,具体说明参考 [6]Binary names ,也可以当作就是类的全限定名(the fully qualified name)。
ClassLoader.loadClass(String name) 和 ClassLoader.loadClass(String name, boolean resolve) 的源码如下
/** * Loads the class with the specified <a href="#name">binary name</a>. * This method searches for classes in the same manner as the {@link * #loadClass(String, boolean)} method. It is invoked by the Java virtual * machine to resolve class references. Invoking this method is equivalent * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name, * false)</tt>}. * * @param name * The <a href="#name">binary name</a> of the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class was not found */ public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } /** * Loads the class with the specified <a href="#name">binary name</a>. The * default implementation of this method searches for classes in the * following order: * * <ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol> * * <p> If the class was found using the above steps, and the * <tt>resolve</tt> flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object. * * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> * * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * * @param name * The <a href="#name">binary name</a> of the class * * @param resolve * If <tt>true</tt> then resolve the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 检查是否已经加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 有父类加载器则委托个父类加载器加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 没有父类加载器的委托给 BootstrapClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 当前类加载器加载 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // 是否需要进行解析,也就是链接 Links the specified class. if (resolve) { resolveClass(c); } return c; } }
可以看到 ClassLoader.loadClass(String name) 方法只做了加载的操作,实际上通过委托给 ClassLoader.loadClass(String name, boolean resolve) 进行加载,参数 resolve=false 表示不会进行链接,更别说是初始化了。从这里的代码中我们也可以看到,ClassLoader 加载起加载前会委托给父类加载器来处理,最终则是没有父类的 BootstrapClassLoader 进行加载,如果都找不到,就由当前的类加载器来加载。这和我们已经了解到的双亲委派模型加载是基本对应的。
这里也可以看到,想要破坏双亲委派模型的话,其实就是省略掉委托给父类加载器加载的过程,直接自己加载,这样就可以在不同的类加载器中拥有“一样”的类而又彼此隔离互不干扰。
3、Class.forName应用之JDBC驱动的的加载
学习 JDBC 的时候,我们经常可以看到这样一个统一的操作流程:
// 注册数据库驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 获取数据库连接 Connection conn = DriverManager.getConnection(url,username,password); PreparedStatement ps = conn.prepareStatement(sql); /// ... // 数据库连接的第一步:先加载需要连接的数据库驱动到 JVM // 通过静态方法 java.lang.Class#forName(String className) 来实现。 try { // 加载 MySQL 的驱动类 Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { // something to do here e.printStackTrace(); } // 成功加载后,会将 Driver 类的实例注册到 DriverManager 类中 // 接下来进行其他操作。。。 // Connection connection = DriverManager.getConnection(url, username, password);
这里调用了 Class.forName(drivername) 之后就可以直接操作了,为什么就能够直接使用了呢?
JDBC规范中要求这个 Driver 实现类必须向 DriverManager 注册自己的一个实例,(以下来自 java.sql.Driver 文档说明):
When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a driver by calling:
Class.forName("foo.bah.Driver")
从上面我们可以知道,Class.forName 调用之后会进行初始化,而初始化则会执行静态代码块,因此 Driver 实现类通过静态代码块来注册驱动实例,就可以达到上面的要求,即任何一个 JDBC Driver 的 Driver 实现类基本都会有如下的类似代码:
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() } }
至此,我们知道了为何只写 Class.forName(drivername) 后就可以直接使用数据库驱动了。
4、参考
- [1]Chapter 12. Execution https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html
- [2]12.2. Loading of Classes and Interfaces https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.2
- [3]12.3. Linking of Classes and Interfaces https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.3
- [4]12.4. Initialization of Classes and Interfaces https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4
- [5]Chapter 5. Loading, Linking, and Initializing https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
- [6]Binary names https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#name
- [7]笔者 github