类加载的方式
主要有两种:
隐式加载:使用 new + 构造方法时,隐式的调用类加载器,加载对应的类到 JVM 中,是最常见的类加载方式。
显式加载:使用 loadClass()、forName() 等方法显式的加载需要的类,对于显式加载这种类加载方式来讲,当我们获取到了 Class 对象后,需要调用 Class 对象的 newInstance() 方法来生成对象的实例。
两种类加载方式的区别:
隐式加载能够直接获取对象的实例,而显式加载需要调用 Class 对象的 newInstance() 方法来生成对象的实例。
隐式加载能够使用有参的构造函数,而使用 Class 对象的 newInstance() 不支持传入参数,如果想使用有参的构造函数,必须通过反射的方式,来获取到该类的有参构造方法。
loadClass() 和 forName() 的区别
首先,不管是使用 loadClass() 还是 forName(),对于任意一个类,我们都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这也是实现反射的重要方法。
那么这两个方法的区别在哪呢?我们通过源码来分析。
- loadClass()
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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;
}
}
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
private native void resolveClass0(Class<?> c);
我们主要来看一下 resolve 这个布尔变量,resolve 即解析,我们可以看到,在重载的 loadClass() 方法中,如果 resolve 为 true 的话,会执行 resolveClass() 这个方法。我们再来看一下 resolveClass() 这个方法中的这一句注释。
在这里插入图片描述
我圈红框的这里,Links the specified class :链接指定的类。
我们再回过头来看看,return loadClass(name, false);,默认的 loadClass() 方法会传入 false,也就是说,使用默认的 loadClass() 方法获得的 Class 对象是还没有执行链接的。那如果你了解 Java 虚拟机的类加载机制的话,这个时候你便应该知道,使用 loadClass() 方法获得的 Class 对象只完成了类加载过程中的第一步:加载,后续的操作均未进行。
- forName()
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
@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 变量。initialize 即初始化。
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
我们可以看到,默认传入的 initialize 为 true.说明使用 Class.forName() 方法获得 Class 对象是已经执行完初始化的了(注意这里指的是类加载过程中的最后一步:初始化,而非是实例化对象操作的初始化)
所以,看到这里相信大家也明白了,这两个方法的区别:
使用 loadClass() 方法获得的 Class 对象只完成了类加载过程中的第一步:加载,后续的操作均未进行。
使用 Class.forName() 方法获得 Class 对象是已经执行完初始化的了
两者的使用场景
对于 Class.forName() 方法,相信大家并不陌生,因为在 Web 开发中,经常会用到使用这个方法来加载 MySQL 的驱动,Class.forName(“com.mysql.jdbc.Driver”);
我们看一下驱动的源码会发现在类 Driver 中有这样一个静态代码块,静态代码块会在类加载过程中的初始化阶段执行。
所以我们现在应该知道了什么时候使用 Class.forName() 方法:在需要对类进行初始化的时候。
static {
try {
//往DriverManager中注册自身驱动
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
那么为什么还要有 loadClass() 呢?
举个小例子,在 Spring IOC 中,在资源加载器获取要读入的字节的时候,即读取一些 Bean 的配置的时候,如果是以 classpath 的方式来加载,就需要使用 ClassLoader 的 loadClass() 方法来加载。之所以这样做,是和 Spring IOC 的 Lazy Loading 有关,即延迟加载。Spring IOC 为了加快初始化的速度,大量的使用了延迟加载技术,而使用 ClassLoader 的 loadClass() 方法不需要执行类加载过程中的链接和初始化的步骤,这样做能有效的加快加载速度,把类的初始化工作留到实际使用到这个类的时候才去执行。