Java编程开发|java开发(Class.forName 和 ClassLoader的区别和联系 | 使用场景 | 多方位解析)

前言

在学习Java反射的时候,少不了同“Class.forName 和 ClassLoader”打交道,然而不深究的难以了解他们的区别和联系,以及各自的使用场景。
文章目的:了解他们的区别和联系以及使用场景,便于后期使用时对他们有清晰的认识。
概述
loadClass() 方法获得的 Class 对象只完成了类的加载,后续的初始化等操作均未进行。
使用 Class.forName() 方法获得 Class 对象,完成了类加载过程各个环节,并执行完类初始化工作。
下面就和我一起来深究一下他们的区别和联系吧。
一、首先了解:类的加载方式 1.隐式加载(最常用) 使用 new + (有参/无参)构造方法时,隐式(偷偷的)的调用类加载器,加载对应的类到 JVM 中,并隐式的完成类的初始化等各项工作,便于随时通过类的别名调用方法或属性,是最常见的类加载方式。
Student stu=new Student(100,"zhangsan"); stu.

2.显式加载(主要用在反射) 显式加载类获取到 Class 对象后,需要调用 Class 对象的 newInstance() 方法来生成对象的实例。最常用的方式就是通过 loadClass()、forName() 方法显式的加载需要的类。
注意:newInstance()方法在jdk9之后的使用有变化。
java8的写法
//该写法在java9中已被废弃 Person p =(Person) Class.forName("com.succ.reflect.test.Person").newInstance();

java9的写法
Person p2 =(Person) Class.forName("com.succ.reflect.test.Person").getDeclaredConstructor().newInstance(); //java9的写法

3.隐式加载和显式加载的区别 1.隐式加载能够直接获取对象的实例,而显式加载需要调用 Class 对象的 newInstance() 方法来生成对象的实例。
2.隐式加载能够使用有参的构造函数,而使用 Class 对象的 newInstance() 不支持传入参数,如果想使用有参的构造函数,必须通过反射的方式,来获取到该类的有参构造方法。
3.显式加载常用在反射,使用更为灵活,可以一个代码对应很多个类,根据传入的class类的名称映射为不同的实现类。
二、Class.forName 和 ClassLoader的区别 1.相同点
两者都可以对类进行加载。
对于任意一个类/对象,我们都能够通过反射能够调用这个类的所有属性方法。
2.不同点
抽象类ClassLoader中实现的方法loadClass,loadClass只是加载,不会解析更不会初始化所反射的类。常用于做懒加载,提高加载速度,使用的时候再通过.newInstance()真正去初始化类。
public class CommWayTest { public static void main(String[] args) throws ClassNotFoundException { ClassLoader loader = CommWayTest.class.getClassLoader(); //注:CommWayTest是当前类的类名 Class c4 = loader.loadClass("com.succ.demo.Person"); Person person=(Person)c4.newInstance(); //使用的时候,再通过.newInstance()进行初始化 } }

Class.forName()加载完毕一个指定类后,会对该类自动进行初始化。常用于加载mysql的jdbc启动。
Class.forName("com.mysql.jdbc.Driver");

3.ClassLoader不解析不初始化类,为什么还要使用它呢?
简单的说,为了提高类的加载速度。
比如:在 Spring IOC (读取Bean配置)时,一般使用的就是 ClassLoader 的 loadClass() 方法来加载。
之所以这样做,是和 Spring IOC 的 Lazy Loading 有关,即延迟加载。Spring IOC 为了加快初始化的速度,大量的使用了延迟加载技术,而使用 ClassLoader 的 loadClass() 方法不需要执行类加载过程中的链接和初始化的步骤,这样做能有效的加快加载速度,把类的初始化工作留到实际使用到这个类的时候才去执行。
三、两者的实现原理 1.ClassLoader实现原理
ClassLoader就是遵循双亲委派(下文有介绍)模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到类的二进制流后放到JVM中,完成加载工作。
源码如下:
public Class loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); //可以看到,该public方法,调用的是protect方法loadClass(name, false),入参是false }

通过上面源码,可以看到该public方法调用的是protect方法loadClass(name, false),入参是false。
重点记忆这个入参resolve,稍后下面的Class.forName()源码中也有类似的用法,不过入参是true!!!
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 (resolve) { resolveClass(c); } return c; }

综上所述:loadClass是抽象类ClassLoader中实现的方法,从源码来看,当调用ClassLoader.loadClass()方法时,调用的是loadClass(name,false),注意到第二个参数false,可以得知loadClass只是加载类,不会对类进行解析和初始化。
2.Class.forName()实现原理 先看一下他的源码
@CallerSensitive public static Class forName(String className) throws ClassNotFoundException { Class caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); }

可以看到他的下一级实现方法是forName0,注意它的一个参数true,对应的是initialize(初始化),就是说这个方法加载完类会对类进行初始化。
private static native Class forName0(String name, boolean initialize, ClassLoader loader, Class caller) throws ClassNotFoundException;

【Java编程开发|java开发(Class.forName 和 ClassLoader的区别和联系 | 使用场景 | 多方位解析)】同时可以看到,forName0的底层实现有涉及ClassLoader类。
四、扩展,双亲委派机制 1.什么是双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
Java编程开发|java开发(Class.forName 和 ClassLoader的区别和联系 | 使用场景 | 多方位解析)
文章图片

2.为什么要使用双亲委派机制
1.防止重复加载同一个.class文件,通过委托去向上级问,加载过了就不用加载了(相当于提前加载父类)。
2.保证核心.class文件不会被串改(即使篡改也不会加载,即使加载也不会是同一个对象),因为不同加载器加载同一个.class文件得到的也不是同一个class对象,从而保证了class执行安全。
总结 1.两者都可以使用类的全量限定名,通过反射获得实体类。
public class CommWayTest { public static void main(String[] args) throws ClassNotFoundException { Class aa=Class.forName("com.mysql.jdbc.Driver"); Class bb=CommWayTest.class.getClassLoader().loadClass("com.mysql.jdbc.Driver"); } }

注:如果你对 Class.forName的使用感兴趣,点击进入(从第四章节开始看)。
2.Class.forName完成类的加载后,会继续完成类的初始化,而ClassLoader.loadClass仅仅只会完成类的加载。
3.它们的使用场景略有不同,前者主要用于加载驱动、后台代码的反射等场景,后者主要配合懒加载(主要用于配合加载配置等)。
4.两者的缺点也十分明显,就是在加载类的时候,只能加载类的无参构造函数,不能直接使用类的有参构造函数。
尾言
在学习过程中,总有帖子避重就轻,不疼不痒,看完了也不知所云,经过反复斟酌推敲,故有此文,当然由于水平有限,文笔拙劣,也可能会出现纰漏,如有发现,请予以指正,共同学习……
附注 猜你可能会对以下内容产生兴趣
java开发:Java反射的意义价值 | 反射的优缺点 | 反射破坏了封装性为什么还要用

    推荐阅读