spi
1、什么是SPI
是Java核心包的接口(放在<java_home>/jre/lib下,被BootstrapClassLoader 加载),但是实现由第三方服务商实现。
第三方服务商的实现类的类名放在 /meta-inf/services 下。但是编译后的class类放在 web-inf/classes文件下
2、SPI是由上层服务提供的接口,加载下层的实现类。
3、SPI的使用场景:jdbc,slf4j,dubbo,spring
打破类加载器的双亲委派模式
Java的历史上一共有3次打破了类加载器的双亲委派模式
1、Java1.0刚出来的时候,这时候还没有双亲委派模式的这个概念(双亲委派是1.2提出来的)
2、osgi Java 9 提出来的模块化
3、spi
这里主要讲SPI模式是如何打破双亲委派模式的。
例如:JDBC。
// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类
// Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/testdb" ;
// 通过java库获取数据库连接
Connection conn = java.sql.DriverManager.
getConnection ( url , "name" , "password" ) ;
由上代码可知,获取JDBC连接,是DriverManger类来获取的,该类在获取连接的时候,加载了静态代码块,找到/META-INF/services的全路径类名,然后在/web-inf/classes中取寻找class 字节码文件。
但是由于,该类的接口在核心包。核心包是由BootstrapClassLoader加载的,该类的实现放在/web-inf/classes 目录下,BootstrapClassLoader加载不了,只能由线程上下文加载器(Tread.currentTread.getContext.ClassLoader,简称TCCL)加载器来加载,TCCL中默认存放了AppClassLoader加载器的引用。这种加载方式是反双亲委派模式的。
普通的加载:Java的基类是放在<java_home>/jre/lib下的,应用类都是调用基类的接口,所以类加载器的双亲委派模式可以保证层次的优先级,保证Java基本不被篡改。通俗的来讲,就是先加载基类,应用类调用基类。
spi加载:spi的接口是放在Java核心包的,核心包加载不了,需要依赖应用类加载器来加载实现类。
Tomcat中的类加载器
在Tomcat目录结构中,有三组目录(/common、/server 、/shared)
1、在common目录下,类库可以被Tomcat和所有的应用程序共同使用
2、在server目录下,类库可以被Tomcat使用,但被应用程序屏蔽
3、在shared目录下,类库可以被所有的应用程序共同使用,但是被TOMCAT屏蔽
灰色背景的3个类加载器是JDK默认提供的类加载器,这3个加载器的作用前面已经介绍过了。而 CommonClassLoader、CatalinaClassLoader、SharedClassLoader 和 WebAppClassLoader 则是 Tomcat 自己定义的类加载器,它们分别加载 /common/、/server/、/shared/* 和 /WebApp/WEB-INF/* 中的 Java 类库。其中 WebApp 类加载器和 Jsp 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 Jsp 类加载器。
从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。
spring加载问题
Q:如果有10个web应用程序都用到了spring的话,spring的文件放在common 目录或者shared目录下供所有的应用程序使用。spring的作用是加载所有的bean,那么放在/WebApp/WEB-INF下的应用程序,spring怎么加载呢?
A:这是个很明显的SPI加载机制。spring的程序是由CommonClassLoader或者ShardClassLoader类加载器加载的。
当应用程序访问时,spring会用到线程的上下文类加载器(线程的类加载器自动设置为了WebAppClassLoader),去加载/WebApp/WEB-INF下的应用程序。所以不管spring放到哪儿,哪个WebApp的应用调用了spring,spring都会用TCCL去加载应用程序。