反射中,Class.forName和ClassLoader区别

一、类加载的过程

装载:通过累的全限定名获取二进制字节流,将二进制字节流转换成方法区中的运行时数据结构,在内存中生成Java.lang.class对象;

链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;

  校验:检查导入类或接口的二进制数据的正确性;(文件格式验证,元数据验证,字节码验证,符号引用验证)

  准备:给类的静态变量分配并初始化存储空间;

  解析:将常量池中的符号引用转成直接引用;

初始化:激活类的静态变量的初始化Java代码和静态Java代码块,并初始化程序员设置的变量值。

PS:解释符号引用和直接引用

1.符号引用:

  符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样,对于其他类的符号引用必须给出类的全名。对于其他类的字段,必须给出类名、字段名以及字段描述符。对于其他类的方法的

  引用必须给出类名、方法名以及方法的描述符。在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。

2.直接引用:

  直接引用可以是

  (1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)

  (2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)

  (3)一个能间接定位到目标的句柄

直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

二、两种加载类方式的区别

Java中Class.forName和classloader都可以用来对类进行加载。

  • Class.forName(“className”);

    其实这种方法调用的是:Class.forName(className, true, ClassLoader.getCallerClassLoader())方法

    参数一:className,需要加载的类的名称。

    参数二:true,是否对class进行初始化(当initialize参数为false时,调用newInstance()方法才会执行static语句。)

    参数三:classLoader,对应的类加载器

  • ClassLoader.laodClass(“className”);

    其实这种方法调用的是:ClassLoader.loadClass(name, false)方法

    参数一:name,需要加载的类的名称

    参数二:false,这个类加载以后是否需要去连接(不需要linking)

可见Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。

而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

所以这就是为什么在jdbc进行数据库操作的时候要用Class.forName进行驱动的加载。

Class.forName(“com.mysql.jdbc.Driver”);//通过这种方式将驱动注册到驱动管理器上
Connection conn = DriverManager.getConnection(“url”,“userName”,“password”);//通过驱动管理器获得相应的连接
.............
.............

再来看一下com.mysql.jdbc.Driver的源码,其中有一段静态代码块:

public class Driver extends NonRegisteringDriver
  implements java.sql.Driver
{
    //注意,这里有一个static的代码块,这个代码块将会在class初始化的时候执行
  static
  {
    try
    {
        //将这个驱动Driver注册到驱动管理器上
      DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
      throw new RuntimeException(“Can’t register driver!”);
    }
  }
}

Class.forName(“com.mysql.jdbc.Driver”)会进行class的初始化,执行static代码块。

也就是说class初始化以后,就会将驱动注册到DriverManageer上,之后才能通过DriverManager去获取相应的连接。

但是要是我们使用ClassLoader.loadClass(com.mysql.jdbc.Driver)的话,不会link,更也不会初始化class。

相应的就不会回将Driver注册到DriverManager上面,后面肯定不能通过DriverManager获取相应的连接。

 

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

IOC使用这种方式个人猜测应该和 Spring IOC 的 Lazy loading 有关, Spring IOC 为了加快初始化速度, 因此大量使用了延时加载技术. 而使用 classloader 不需要执行类中的初始化代码, 可以加快加载速度, 把类的初始化工作留到实际使用到这个类的时候

posted @ 2022-05-26 10:27  Arbitrary233  阅读(204)  评论(0编辑  收藏  举报