java 类加载器,SPI机制,spring factories的原理

一、java类加载器原理:

1、java自带的类加载器有三种:bootstrap classLoader,extension classLoader,App classLoader。

bootstrap ClassLoader: 启动类加载器,负责加载系统的核心类库,如lib下的jar包。

extension ClassLoader:扩展类加载器,主要负责加载lib下的ext扩展类。

app ClassLoader:应用类加载器,主要负责加载应用的classpath目录下的class文件。

2、双亲委派加载的机制:系统启动后,应用类加载器加载应用类,发起双亲委派机制,让上级扩展类加载器去加载,扩展类加载器让上级的启动类加载器加载,当启动类加载器加载不到,再有扩展类加载器加载,若扩展类加载器也加载不了,则让应用类加载器加载。

当加载某一个class类时,这个class类中有调用的其他类也应一次加载,对于应用的这些类也通过上面双亲委派机制加载。

3、类加载器具有传递性,当开启子线程时,会使用主线程的类加载器加载类文件。所有加载的类都存于JVM中,jvm中每一个class对象都绑定有对应的类加载器,所以在jvm中一样的class全限名的class对象不一定相等,因为类加载器不一样。(为什么在系统中自定义一个java.lang.Integer,加载会报错,因为加载类时,双亲委派机制以及使用了启动类加载器加载了integer到jvm中,所以自定义的Integer在加载会报错,如果这个自定义的integer没有在classPath里,在其他地方,应用类加载器都没有加载到,使用自定义的类加载器,通过字节码的方式加载这个文件,这里就需要分为两种情况,第一种你自定义的类加载器中如果没有使用双亲委派机制,则可以加载,如果使用了双亲委派机制,则返回的是系统类加载器加载的class)

4、打破双亲委派:

   (1)自定义类加载器,自定义一个类,实现ClassLoader接口。覆盖loadClass方法。通过文件流的方式获取文件字节码,再通过父级的 defineClass生成Class对象。就没有走父级类加载器去加载。
   如果不需要打破双亲委派,则覆盖findClass即可。

   (2)SPI机制,第三方jar包通过ServiceLoader.load进行加载,首先通过双亲委派机制,bootstrap类加载器加载到了ServiceLoader类,然后在Spring boot中通过ServiceLoader去加载第三方jar中的类时,由于类加载器的传递性,只能通过bootStrap去加载第三方jar,然后顶级类加载器肯定加载不了这个jar,而且当前的起始点已经是顶级,不能再往下传递,所以就只能通过当前线程上下文中的类加载器去加载第三方jar包的类(如果没有设置,则使用父级线程的类加载器),这里就打破了双亲委派。

这里估计很多人不明白当前线程上下文类加载器是啥?给他有什么关系? 首先当前线程上下文问类加载器是生成新的线程后,手动设置的当前类加载器,以加载mysql驱动举例,如下图,业务运行时,需要获取connection连接,就需要根据当前类加载器去加载(至少是系统类加载器以下)

 (3)使用OSGI热部署,对jar包进行更新,卸载打破双亲委派。

二、java的SPI机制打破双亲委派进行加载类。

  1、Java的SPI实现原理:使用ServiceLoader.load(接口.class);  获取这些实现类对象。ServiceLoader的实现原理是:

第三方jar包下的META-INFO/services目录下创建一个以接口全限名为命名文件,内容为这个接口的实现类,实现类放于该jar包中。在启动的系统中扫描所有jar下的META-INFO/services目录,提取里面的实现类。然后创建自定义类加载器加载(覆写findClass方法,用流的方式加载这个类文件)。使用 Class.forName(实现类全限名, false, classLoader); 加载这个实现类到jvm中。然后再进行实例化。所以实现类必须有无参构造方法。

2、使用SPI的条件:
      a、第三方jar里需要有指定接口的实现类。并在第三方打的jar包里有META-INFO/services目录下创建一个以接口全限名命名的文件,文件里的内容是这个接口的实现类的全限名。
      b、这个第三方jar包需要放入到classPath目录里。
      c、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
       d、实现类必须有无参构造方法。

3、应用场景:解耦,可插拔。

     a、数据库驱动加载接口实现类的加载 :JDBC加载不同类型数据库的驱动

系统通过加载java自身的DirverManager类,并加载里面的static静态块的init方法,在通过SPI机制,ServiceLoader.load(Dirver.class)去加载所有jar中有配置META-INFO/service下java.sql.Driver文件里面的类。
这里如果引入了mysql驱动包,就会把mysql的META-INFO/service/java.sql.Driver里的mysql的Driver类加载起来,在这个mysql的Driver里会执行一个静态块里的DriverManager.registerDriver(new com.mysql.jdbc.Driver());这样就将mysql的驱动注入到java的driver集合中。在业务运行时阶段,程序里面通过DriverManager获取getConnection(url)时(当前的类加载器至少是系统类加载器以下),就会遍历driver集合driver.connect获取连接,哪一个能获取到就返回哪个connection连接。

估计还有同学疑惑,为啥不是类加载器直接加载,而是通过SPI方式加载?
​​​​​​​我的回答是:如果你在程序中显示的引用jar里的类,也能直接正常加载,但是SPI的优点是解耦,可插拔。我把jar删掉,不影响系统运行。


     b、日志门面接口实现类加载 : SLF4J加载不同提供商的日志实现类

三、spring boot的SPI机制,即spring factories 实现原理:

    在spring-core包里定义了SpringFactoryLoader类,这个类会检索所有jar包下的META-INFO/factories文件。然后在程序中读取这些配置文件加载到jvm中,如有spring的一些注解,则进行解析。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值