目录
1、SPI的前世今生
①什么是SPI
SPI全程:Service Provider Interface(服务提供者接口)。
SPI将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方.
②为什么要使用SPI
为了实现在模块装配的时候不用在程序里面动态指明,这就需要一种服务发现机制.Java SPI 就是提供了这样一个机制:为某个接口寻找服务实现的机制。这有点类似 IoC 的思想,将装配的控制权移交到了程序之外.
③SPI与API的区别
API 直接被应用开发人员使用,SPI 被框架扩展人员使用.
API(Application Programming Interface)大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现.
SPI(Service Provider Interface)调用方来选择自己需要的实现方式.
2、JDK SPI
JDK SPI设计原则
- 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名.
- 接口实现类所在的jar包放在主程序的classpath中.
- 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM.
加载文件样式以及内容
3、spring SPI
spring SPI设计原则
- 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF目录下创建一个spring.factories的文件,内容为(key,value)形式,key是接口全路径名,value是实现类的全限定名.
- 接口实现类所在的jar包放在主程序的classpath中.
- 主程序通过org.springframework.core.io.support.SpringFactoriesLoader动态装载实现模块,它通过扫描META-INF/spring.factories配置文件找到实现类的全限定名,把类加载到JVM.
加载文件样式以及内容
4、dubbo SPI实现原理
①JDK SPI的不足
- 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现(精准性低)
- 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们
- 扩展如果依赖其他的扩展,做不到自动注入和装配
- 不提供类似于Spring的IOC和AOP功能
- 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持
②dubbo SPI设计原则
- dubbo沿用了JDK SPI的功能,还增加了自己的功能,在jar包的除了加载META-INF/services以外,还有META-INF/dubbo/以及META-INF/dubbo/internal/目录下创建一个以“接口全限定名”为命名的文件,内容为(key,value)形式,key是对象命名,value是实现类的全限定名.
- 接口实现类所在的jar包放在主程序的classpath中.
- 主程序通过com.alibaba.dubbo.common.extension.ExtensionLoader动态装载实现模块,它通过扫描META-INF/dubbo/以及META-INF/dubbo/internal/配置文件找到实现类的全限定名,把类加载到JVM.
③dubbo SPI实现机制
加载文件样式以及内容
//JDK SPI加载路径
private static final String SERVICES_DIRECTORY = "META-INF/services/";
//DUBBO
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
//DUBBO
private static final String DUBBO_INTERNAL_DIRECTORY = "META-INF/dubbo/internal/";
dubbo三个加载目录:META-INF/services/ ,META-INF/dubbo/ ,META-INF/dubbo/internal/
加载方法loadFile部分代码,加载文件已经遍历元素代码滤过 ,代码如下:
private void loadFile(Map<String, Class<?>> extensionClasses, String dir){
...........省略多行代码..................
Class<?> clazz = Class.forName(line, true, classLoader);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//判断是否是适配类
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else {
try {
//如果实现类上没有Adaptive注解,会做如下处理:
如果这个带参的构造函数存在 , 则说明当前的读到的Class是有包装类的 ,则先将Class的类对象clazz放入ExtensionLoader类的实例属性 cachedWrapperClasses中
clazz.getConstructor(type);
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
//如果带参的构造函数不存在,则执行以下逻辑
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
//判断类上是否有Activate注解
//如果Activate注解存在则放入到ExtensionLoader类的实例属性cachedActivates中
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
//没有放入缓存
if (!cachedNames.containsKey(clazz)) {
//以clazz为key,clazz的name为value缓存到ExtensionLoader类的实例属性cachedNames中
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
//以clazz的name为key,以clazz为value缓存到传入的参数extensionClasses中
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
}
...........省略多行代码..................
loadFile方法流程总结
- 通过Class.forName(String name, boolean initialize,
ClassLoader loader)将类加载出来,当前name传入值是当前spi文件里的value值,例如:spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory,其中的com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory - 如果实现类上有Adaptive注解,则将读到的类赋值给ExtensionLoader类的实例属性cachedAdaptiveClass
- 没有Adaptive注解,如果这个带参的构造函数存在 , 则说明当前的读到的Class是有包装类的 ,则先将Class的类对象clazz放入ExtensionLoader类的实例属性 cachedWrapperClasses中
- 如果带参的构造函数不存在,判断Activate注解存在则放入到ExtensionLoader类的实例属性cachedActivates中
- 以clazz为key,clazz的name为value缓存到ExtensionLoader类的实例属性cachedNames中
- 以clazz的name为key,以clazz为value缓存到传入的参数extensionClasses中
文件信息加载到缓存之后,就可以进行IOC和AOP的操作了。
为此dubbo还实现了自己的IOC和AOP,也是拓展机制里精彩的一环。