在Java的反射中,Class.forName和ClassLoader的区别

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 在Java的反射中,Class.forName和ClassLoader的区别


前言

最近在面试过程中有被问到,在Java反射中Class.forName()加载类和使用ClassLoader加载类的区别。当时没有想出来后来自己研究了一下就写下来记录一下。

Java类装载过程

加载->验证->准备->解析->初始化->使用->销毁

装载:通过累的全限定名获取二进制字节流,将二进制字节流转换成方法区中的运行时数据结构,在内存中生成Java.lang.class对象; 
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的; 
  校验:检查导入类或接口的二进制数据的正确性;(文件格式验证,元数据验证,字节码验证,符号引用验证) 
  准备:给类的静态变量分配并初始化存储空间; 
  解析:将常量池中的符号引用转成直接引用; 
初始化:激活类的静态变量的初始化Java代码和静态Java代码块,并初始化程序员设置的变量值。

解释

在java中Class.forName()和ClassLoader都可以对类进行加载。ClassLoader就是遵循 双亲委派模型 最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。

Class.forName(String className);这个方法的源码是

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

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

也可以调用Class.forName(String name, boolean initialize,ClassLoader loader)方法来手动选择在加载类的时候是否要对类进行初始化。Class.forName(String name, boolean initialize,ClassLoader 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);
 }
复制代码

源码中的注释只摘取了一部分,其中对参数initialize的描述是: if {@code true} the class will be initialized. 意思就是说:如果参数为true,则加载的类将会被初始化。

下面还是举例来说明结果吧:

一个含有静态代码块、静态变量、赋值给静态变量的静态方法的类

public class ClassForName {
 //静态代码块
 static {
 System.out.println("执行了静态代码块");
 }
 //静态变量
 private static String staticFiled = staticMethod();
 //赋值静态变量的静态方法
 public static String staticMethod(){
 System.out.println("执行了静态方法");
 return "给静态字段赋值了";
 }
}
复制代码

测试方法:

public class MyTest {
 @Test
 public void test44(){
 try {
 Class.forName("com.test.mytest.ClassForName");
 System.out.println("#########分割符(上面是Class.forName的加载过程,下面是ClassLoader的加载过程)##########");
 ClassLoader.getSystemClassLoader().loadClass("com.test.mytest.ClassForName");
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 }
}
复制代码

运行结果:

执行了静态代码块
执行了静态方法
#########分割符(上面是Class.forName的加载过程,下面是ClassLoader的加载过程)##########
复制代码

根据运行结果得出Class.forName加载类是将类进了初始化,而ClassLoader的loadClass并没有对类进行初始化,只是把类加载到了虚拟机中。

在我们熟悉的Spring框架中的IOC的实现就是使用的ClassLoader。

而在我们使用JDBC时通常是使用Class.forName()方法来加载数据库连接驱动。这是因为在JDBC规范中明确要求Driver(数据库驱动)类必须向DriverManager注册自己。

以MySQL的驱动为例解释:

public class Driver extends NonRegisteringDriver implements java.sql.Driver { 
 // ~ Static fields/initializers 
 // --------------------------------------------- 
 
 // 
 // Register ourselves with the DriverManager 
 // 
 static { 
 try { 
 java.sql.DriverManager.registerDriver(new Driver()); 
 } catch (SQLException E) { 
 throw new RuntimeException("Can't register driver!"); 
 } 
 } 
 
 // ~ Constructors 
 // ----------------------------------------------------------- 
 
 /** 
 * 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()的原因了。




目录
相关文章
|
12天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
20天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
20天前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
13 0
[Java]反射
|
1月前
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
25 2
|
23天前
|
Java
Java代码解释静态代理和动态代理的区别
### 静态代理与动态代理简介 **静态代理**:代理类在编译时已确定,目标对象和代理对象都实现同一接口。代理类包含对目标对象的引用,并在调用方法时添加额外操作。 **动态代理**:利用Java反射机制在运行时生成代理类,更加灵活。通过`Proxy`类和`InvocationHandler`接口实现,无需提前知道接口的具体实现细节。 示例代码展示了两种代理方式的实现,静态代理需要手动创建代理对象,而动态代理通过反射机制自动创建。
|
25天前
|
缓存 算法 Java
Java 中线程和纤程Fiber的区别是什么?
【10月更文挑战第14天】
58 0
|
29天前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
16 0
|
存储 SQL Java
Java反射读取注解信息
Java反射读取注解信息
69 0
|
JSON 安全 Java
|
6月前
|
安全 Java API
Java中的反射(通过反射获取类的结构、invoke方法、获取注解)
Java中的反射(通过反射获取类的结构、invoke方法、获取注解)