java类加载器以及spi

类加载器概述:

每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。
  那么 .class 文件什么时候会被类加载器加载到 JVM 中那?比如执行 new 操作的时候,我们使用 Class.forName(“ 包路径 + 类名 “)、Class.forName(“ 包路径 + 类名 “,ClassLoader)、ClassLoader.loadClass(“ 包路径 + 类名 “) 就触发了类加载器去类加载对应的路径去查找 *.class,并创建 Class 对象。另外需要注意的是除去 new 操作外,其他几种方式加载字节码到内存后只是生产一个 Class 对象,要产生具体的对象实例还需要使用 Class 对象 .newInstance() 函数来创建。

类的生命周期:

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们开始的顺序如下图所示:
在这里插入图片描述

加载:

通过类的全限定名来获取定义此类的二进制字节流

将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构

在堆内存中生成代表此类的java.lang.Class对象,作为该类访问入口.
  在这里插入图片描述

验证:

连接阶段第一步.验证的目的是确保Class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:

文件格式验证

源数据验证

字节码验证

符号引用验证

准备:连接阶段第二步,
正式为类变量分配内存并设置变量的初始值.(仅包含类变量,不包含实例变量).
解释如下:  
为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
默认初始值如下:

1.八种基本数据类型默认的初始值是0
2.引用类型默认的初始值是null
3.有static final修饰的会直接赋值,例如:static final int x=10;则默认就是10.
解析:连接阶段第三步,
虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等…
说白了就是jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。
初始化:
类的初始化是类加载过程的最后一步,在该阶段,才真正意义上的开始执行类中定义的java程序代码.该阶段会执行类构造器.
类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
使用:
使用该类所提供的功能.

卸载:
从内存中释放.
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
类加载器

java.lang.classLoader

在这里插入图片描述
 大部分java程序会使用以下3中系统提供的类加载器:

启动类加载器(Bootstrap ClassLoader):

这个类加载器负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.

扩展类加载器(Extendsion ClassLoader):
    这个类加载器负责加载<JAVA_HOME>\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

应用程序类加载器(Application ClassLoader):

这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.

除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类.
  在这里插入图片描述
  **

类加载器的双亲委派模型:

**
  「双亲委托模式」指的就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,如果父类加载器可以完成类加载任务,就成功返回;**只有父类加载器无法完成此加载任务时,自己才去加载。
   双亲委托模型的工作过程:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它的搜索范围之中没有找到这个类)时,子加载器才会尝试着自己去加载。
   在这里插入图片描述
**
  Class.forname()与ClassLoader.loadClass():
  Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

Class.forname()与ClassLoader.loadClass():
  Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

Class.forname()与ClassLoader.loadClass():
  Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

(1)优点

避免类库重复加载
安全,将核心类库与用户类库隔离,用户不能通过加载器替换核心类库,如String类。
(2)弊端

委托永远是子加载器去请求父加载器,是单向的,即上层的类加载器无法访问下层的类加载器所加载的类:
举个例子,假设「BootStrap」中提供了一个接口,及一个创建其实例的工厂方法,但是该接口的实现类在「System」中,那么就会出现工厂方法无法创建在「System」加载的类的实例的问题。拥有这样问题的组件有很多,比如JDBC、Xml parser等。

3、如何解决弊端——使用「SPI」

「SPI」 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现, 简单来说,它就是一种动态替换发现的机制。
API和SPI的区别
API 直接被应用开发人员使用,SPI 被框架扩展人员使用
API Application Programming Interface

大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。

SPI Service Provider Interface

而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。

3、JDBC举例

下面以JDBC为例,介绍「SPI」机制。

在JDBC4.0之前,我们开发有连接数据库的时候,通常会用Class.forName(“com.mysql.jdbc.Driver”)这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动,直接获取连接就可以了,现在这种方式就是使用了Java的「SPI」扩展机制来实现。
jdbc的获取连接的两种方式
添加链接描述

(1)接口定义

JDBC在java.sql.Driver只定义了接口。
在这里插入图片描述
(2)厂商实现

这里以MySQL为例,在mysql-connector-java-5.1.39.jar包里的META-INF/services目录下可以找到一个java.sql.Driver文件,文件内容是一个类名,这个名叫com.mysql.cj.jdbc.Driver的类就是MySQL针对JDBC中定义的接口的实现。
在这里插入图片描述
在这里插入图片描述
(3)如何使用

在我们的应用里面,我们就可以直接连接MySQL了。

Connection conn = DriverManager.getConnection(url,username,password);
显然语句并没有加载实现类,这里就涉及到使用「SPI」扩展机制来查找相关驱动了,接下来,我们结合源码探究一下这是如何实现的。

4、源码解析
关于驱动的查找其实都在DriverManager中,DriverManager位于java.sql包里,用来获取数据库连接,在DriverManager中有一个静态代码块如下:
static {
loadInitialDrivers();
println(“JDBC DriverManager initialized”);
}
loadInitialDrivers方法用于实例化驱动,由3部分构成:
(1)获取有关驱动的名称
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty(“jdbc.drivers”);
}
});
} catch (Exception ex) {
drivers = null;
}
drivers保存驱动的定义
(2)加载并实例化驱动
在这里插入图片描述
比较关键的地方是ServiceLoader.load
其中ServiceLoader.load(Driver.class)方法源码:
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

   java的双亲委托类加载机制(ClassLoaderA -> System class loader -> Extension class loader -> Bootstrap class loader)可以保证核心类的正常安全加载。但是右边的 Bootstrap class loader 所加载的代码需要反过来去找委派链靠左边的 ClassLoader A 去加载东西的时候,就需要委派链左边的 ClassLoader 设置为线程的上下文加载器即可。

每一个线程都有自己的ContextClassLoader,默认以SystemClassLoader为ContextClassLoader。通过Thread.currentThread().getContextClassLoader(),可以把一个ClassLoader置于一个线程的实例之中,使该ClassLoader成为一个相对共享的实例,这样即使是启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了。
参考链接:
https://www.cnblogs.com/hiyujie/p/wo-xueJava1ClassLoader-yu-shuang-qin-wei-tuo-mo-sh.html

猜你喜欢

转载自blog.csdn.net/u014362882/article/details/82916494