一 介绍
spring源码中,ClassUtils工具类对于Class字节码的处理做了大量的工具封装
其中,根据类的文件描述获取对应字节码的方法forName(String name)可谓是对于Class.forName(String name)和ClassLoader.loadClass(String name)功能进行了扩展增强,
/**
* Class.forName()的进化版,返回类的实例对象
* Replacement for {@code Class.forName()} that also returns Class instances
* for primitives (e.g. "int") and array class names (e.g. "String[]").
* Furthermore, it is also capable of resolving inner class names in Java source
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
* @param name the name of the Class
* @param classLoader the class loader to use
* (may be {@code null}, which indicates the default class loader)
* @return a class instance for the supplied name
* @throws ClassNotFoundException if the class was not found
* @throws LinkageError if the class file could not be loaded
* @see Class#forName(String, boolean, ClassLoader)
*/
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Assert.notNull(name, "Name must not be null");
// 判断是否作为原始类进行解析
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = commonClassCache.get(name);
}
if (clazz != null) {
return clazz;
}
// "java.lang.String[]" style arrays
if (name.endsWith(ARRAY_SUFFIX)) {
String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[Ljava.lang.String;" style arrays
if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[[I" or "[[Ljava.lang.String;" style arrays
if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// TODO:为什么不直接利用classLoader实例进行判断操作,而需要重新创建一个引用?
ClassLoader clToUse = classLoader;
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
try {
return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
}
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
// java.lang$Provider
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
}
还有,我们在JDBC编程的时候,经常会遇到这么一行代码,
try{
Class.forName("com.mysql.cj.jdbc.Driver");//加载mysql的jdbc驱动
...
}
......
那么针对于Class.forName(String name)和ClassLoader.loadClass(String name),之间有什么样的区别,我们就来简单的分析一下。
二 示例
举一个简单的示例:
MyTestBean.class
package com.zcswl.test;
/**
* @author zhoucg
* @date 2020-01-08 14:14
*/
public class MyTestBean {
static {
System.out.println("这是一个静态方法(代码)块");
}
private static String executeStaticMethod = executeStaticMethod();
private static String executeStaticMethod() {
System.out.println("这是一个静态方法");
return "1";
}
}
我们使用 Class.forName(String name) 进行执行
Class<?> aClass = Class.forName("com.zcswl.test.MyTestBean");
输出结果:
这是一个静态方法(代码)块
这是一个静态方法
Process finished with exit code 0
我们再通过 ClassLoader.loadClass(String name) 进行执行
Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("com.zcswl.test.MyTestBean");
输出结果:
Process finished with exit code 0
通过输出结果我们可以发现,
通过Class.forName(String name)方法获取MyTestBean的字节码对象的时候,是会执行对应的静态代码块和初始化对应的静态属性,并执行了对应的静态方法
通过ClassLoad.loadClass(String name)并不会执行对应的静态代码块和初始化对应的静态属性
对于这个结论的分析,我们可以先从一个java类的加载到初始化的步骤进行分析,然后再从源码的角度分析
一个完整的类,它的完整使用过程是会经历加载,验证,准备,解析,初始化,使用,到卸载的过程。
首先,jvm虚拟机会去验证当前的类是否加载到内存中,如果没有,通过ClassLoader类加载器,通过双亲委派的方式将一个类加载到内存,实际上 ClassLoader.loadClass(String name) 方法就是对于JVM虚拟机层面,仅仅是将对应的类加载到内存中,准备后续的验证等操作,因此,我们会发现,其不会执行对应的静态代码块,和实例化当前的静态属性。(初始化才会执行)
接着,会进行验证当前Class字节码的信息是否符合jvm虚拟机,并正式为类变量(static 修饰的变量)分配内存,并设置类变量的初始化值,然后会将字节码对应的常量池内的符号引用替换为直接引用的过程。
接下来就是进入初始化阶段,初始化阶段会执行对应的类静态构造代码块,并为类变量赋值,其实,针对于Class.forName(String name),就是会触发MyTestBean的初始化阶段。
最后是使用对象,和内存释放的阶段
三 源码
Class.forName(String name)的源码如下:
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
Reflection.getCallerClass();是获取当前调用forName(String name)方法对应的Class字节码对象,(这里有一个比较难理解的@CallerSensitive注解,自己可以百度了解一下)
接着,通过ClassLoader.getClassLoad(caller)获取当前类加载器,同时调用forName0()方法
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
该方法是一个native方法,其中,第二个参数initialize代表的意思就是是否进行初始化操作,如果设置为true,代表会进行初始化操作,所以会执行对应的静态方法块,以及赋值对应的静态变量(类变量)
我们会发现,通过Class.forName(String name)调用forName0()将对应的initialize传参为true,
其实,Class.forName还有一个重载的方法
/ * @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
* /
@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为false,使对应的类不进行初始化操作,只进行加载
然后,我们再来看ClassLoader.loadClass(String name)源码
/**
* 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);
}
该方法是通过类的全限定名加载对应的类,并由JVM虚拟机调用它来解析类的引用。实际上,它同样有一个受保护的重载的方法
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 {
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();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
大致的逻辑就是,
- 1,首先调用findLoadedClass(String name) 查找当前的类是否已经被加载。
- 2,如果parent
是null,通过内置的类加载器(这里其实就是BootstrapClassLoader)进行加载:(api文档)Invoke the {@link #loadClass(String) loadClass} method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead. - 3,如果父类加载器任未找到,由当前类加载器的findClass(String name ) 查找,
这里有一个resolve boolean类型的变量,如果未true的话,会调用resolveClass©方法
/**
* Links the specified class. This (misleadingly named) method may be
* used by a class loader to link a class. If the class <tt>c</tt> has
* already been linked, then this method simply returns. Otherwise, the
* class is linked as described in the "Execution" chapter of
* <cite>The Java™ Language Specification</cite>.
*
* @param c
* The class to link
*
* @throws NullPointerException
* If <tt>c</tt> is <tt>null</tt>.
*
* @see #defineClass(String, byte[], int, int)
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
该方法实际上是会触发对应加载过程的连接阶段,Links the specified class
四 ClassLoader类加载器的执行过程
对于类加载器的执行过程以及jvm中常见的类加载器,在之前的Java字节码结构 我的博客中,已经有过一次介绍。
下面,通过一个具体的代码示例分析ClassLoader的类加载过程
//创建当前系统对应的类加载器,
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//通过类加载器加载MyTestBean
Class<?> aClass1 = systemClassLoader.loadClass("com.zcswl.test.MyTestBean");
首先,我们通过Classloader.getSystemClassLoader()获取当前应用的类加载器,通过调用我们可以发现,其是一个Launcher内部类AppClassLoader 类型,
我们再进入到sun.misc.Launcher源码,它是一个jvm虚拟机的入口应用。
我们知道系统内置的三个ClassLoader加载器分别是:
BootstrapClassLoader
ExtClassLoader
AppClassLoader
Launcher类的无参的构造函数中,刚好创建了对应AppClassLoader和ExtClassLoader
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if(var2 != null) {
SecurityManager var3 = null;
if(!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if(var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
首先是创建Launcher.ExtClassLoader 类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
一通源码追踪之后,我们会发现,ExtClassLoader类加载器会通过getExtDirs()方法获取到系统中java.ext.dirs的配置信息,
我们首先测试一下系统中java.ext.dirs的配置结果:
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
因此,ExtClassLoader加载的路径是对应的jre/lib/ext中的jar包数据
然后再创建Launcher.AppClassLoader加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
加载对应java.class.path路径下的
Launcher的构造函数中中并没有看见BootstrapClassLoader创建,但通过System.getProperty(“sun.boot.class.path”)得到了字符串bootClassPath,这个就是BootstrapClassLoader加载的jar包路径。
首先,
Class<?> aClass1 = systemClassLoader.loadClass("com.zcswl.test.MyTestBean");
会调用Launcher#AppClassLoader的loadClass(String name)方法
然后执行super.loadClass(String name,boolean resolve ),这个方法是再抽象类ClassLoader中定义,(这里,就进入到上面我们分析的,首先会找到parent父类加载器进行执行)
注意的是,AppClassLoader的父类加载器是ExtClassLoader,而ExtClassLoader的父类加载器并不是BootstrapClassLoader,
关于这一点,可以看一下源码:
ExtClassLoader在Launcher中的创建
Launcher()->Launcher.ExtClassLoader.getExtClassLoader();->createExtClassLoader()->new Launcher.ExtClassLoader(var1);
可以清晰的看到,ExtClassLoader最终赋值给parent的就是null
对于AppClassLoader:
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
他会首先将已经创建好的ExtClassLoader作为参数传递到Launcher.AppClassLoader.getAppClassLoader 中,然后
return new Launcher.AppClassLoader(var1x, var0)->super(var1, var2, Launcher.factory)
最终,我们会发现AppClassLoader赋值给parent就是上面创建的ExtClassLoader对象
回到正题,我们会发现,他会调用对应的
c = parent.loadClass(name, false);
将加载类的请求提交给parent的ClassLoader
然后,我们会发现,此时的ExtClassLoader的parent就是null
最后,会执行findClass(String name)方法,进入到URLClassLoader#findClass(final String name)中
该方法会从对应的类加载路径中,搜索,对应ExtClassLoader,自然是搜索不到,返回null,最终,是交由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
完毕