一.知识回顾
【0.SpringBoot专栏的相关文章都在这里哟,后续更多的文章内容可以点击查看】【1.SpringBoot初识之Spring注解发展流程以及常用的Spring和SpringBoot注解】
二.什么是SPI?为什么要学习SPI呢?SPI和我们要学习的SpringBoot框架又有什么关联呢?
- 因为在SpringBoot的自动装配中其实有使用到SPI机制,所以掌握了这部分对于SpringBoot的学习还是很有帮助的。
- SPI,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。
三.案例实操学习
3.1 先创建一个定义接口的项目—SPIDemo
3.2 在该项目下使用Maven进行打包
3.3 再创建一个项目—TestSPI
3.4 先TestSPI项目中导入项目SPIDemo项目的依赖
<dependencies>
<dependency>
<groupId>com.ljw</groupId>
<artifactId>SPIDemo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
3.5 然后在TestSPI项目中创建接口的实现
import com.ljw.spi.SPIDemo;
public class Test implements SPIDemo {
@Override
public void hello() {
System.out.println("spi.....hello");
}
}
3.6 然后在TestSPI项目中的resources目录下创建 META-INF/services 目录,然后在目录中创建一个文件,名称必须是SPIDemo定义的接口的全类路径名称。然后在文件中写上TestSPI接口的实现类的全类路径名称。
3.7 在该项目TestSPI下使用Maven进行打包
3.8 再创建一个测试项目—TestSpiDemo
3.9 导入TestSPI的依赖并测试
然后在测试的项目中测试
import java.util.Iterator;
import java.util.ServiceLoader;
public class App{
public static void main( String[] args ){
ServiceLoader<SPIDemo> providers = ServiceLoader.load(SPIDemo.class);
Iterator<SPIDemo> iterator = providers.iterator();
while(iterator.hasNext()){
SPIDemo next = iterator.next();
next.hello();
}
}
}
四.SPI机制核心源码学习
进入ServiceLoader类,首先来看下ServiceLoader的类结构
// 配置文件的路径
private static final String PREFIX = "META-INF/services/";
// 加载的服务 类或者接口
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 访问权限的上下文对象
private final AccessControlContext acc;
// 保存已经加载的服务类
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 内部类,真正加载服务类
private LazyIterator lookupIterator;
load方法创建了一些属性,重要的是实例化了内部类,LazyIterator
public final class ServiceLoader<S> implements Iterable<S>
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//要加载的接口
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//类加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//访问控制器
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
//先清空
providers.clear();
//实例化内部类
LazyIterator lookupIterator = new LazyIterator(service, loader);
}
}
查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。
private class LazyIterator implements Iterator<S>{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private boolean hasNextService() {
//第二次调用的时候,已经解析完成了,直接返回
if (nextName != null) {
return true;
}
if (configs == null) {
//META-INF/services/ 加上接口的全限定类名,就是文件服务类的文件
//META-INF/services/com.viewscenes.netsupervisor.spi.SPIService
String fullName = PREFIX + service.getName();
//将文件路径转成URL对象
configs = loader.getResources(fullName);
}
while ((pending == null) || !pending.hasNext()) {
//解析URL文件对象,读取内容,最后返回
pending = parse(service, configs.nextElement());
}
//拿到第一个实现类的类名
nextName = pending.next();
return true;
}
}
创建实例对象,当然,调用next方法的时候,实际调用到的是,lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。
private class LazyIterator implements Iterator<S>{
private S nextService() {
//全限定类名
String cn = nextName;
nextName = null;
//创建类的Class对象
Class<?> c = Class.forName(cn, false, loader);
//通过newInstance实例化
S p = service.cast(c.newInstance());
//放入集合,返回实例
providers.put(cn, p);
return p;
}
}
好了,【SpringBoot自动装配之SPI机制&SPI案例实操学习&SPI机制核心源码学习】就先学习到这里,关于SpringBoot更多内容持续创作学习中。