Class.forName(String name)与ClassLoader.loadClass(String name)的区别


一 介绍

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&trade; 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下面的路径。找到就返回,没有找到,让子加载器自己去找。

完毕

发布了55 篇原创文章 · 获赞 14 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zcswl7961/article/details/103890638