## Java SPI
SPI的全名为Service Provider Interface, java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
SPI的使用是寻找服务实现,例如,我们在项目A中定义了接口Developer,之后可以使用ServiceLoader加载Developer的实现类,实现类可能有B项目或C项目实现。
A项目:
```
package com.shisj.study.dubbo.spi.Developer;
public interface Developer {
public String getPrograme();
}
```
B项目中提供了两种实现类:
```
package com.shisj.study.dubbo.spi.impl;
public class JavaDeveloper implements Developer{
public String getPrograme() {
return "Java";
}
}
```
```
package com.shisj.study.dubbo.spi.impl;
public class PythonDeveloper implements Developer{
public String getPrograme() {
return "Python";
}
}
```
在B项目的classpath下创建META-INF/services文件夹,内部包含com.shisj.study.dubbo.spi.Developer 文件,这是接口的全名。里面的内容是该借接口的实现类;
```
com.shisj.study.dubbo.spi.impl.JavaDeveloper
com.shisj.study.dubbo.spi.impl.PythonDeveloper
```
将B项目打包成jar包并引用至A项目中,然后我们就可以通过ServiceLoader查找实现类,
```
public ServiceLoader<Developer> serviceloader = ServiceLoader.load(Developer.class);
for (Developer dev : serviceloader) {
System.out.println("out." + dev.getPrograme());
}
// out.Java
// out.Python
```
## Dubbo扩展
dubbo扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。具体的配置文件在classpath的/META-INF/dubbo/internal文件夹里面。dubbo的扩展机制与spi不同,其文件中的内容记录了`key=class`,原因是:
>当扩展点的static字段或方法签名上引用了三方库, 如果三方库不存在,会导致类初始化失败, Extension标识Dubbo就拿不到了,异常信息就和配置对应不起来。
比如: Extension("mina")加载失败, 当用户配置使用mina时,就会报找不到扩展点, 而不是报加载扩展点失败,以及失败原因。
### ExtensionLoader
在dubbo源码中,对可扩展点的加载都是通过ExtensionLoader类获取实例的,例如:
```
// 获取Compiler接口的实现类
ExtensionLoader<Compiler> loader1 = ExtensionLoader.getExtensionLoader(Compiler.class);
Compiler compile1 = loader1.getAdaptiveExtension();
System.out.println(compile1.getClass());
Compiler compile2 = loader1.getExtension("jdk");
System.out.println(compile2.getClass());
```
这样做的目的是扩展性强,使用者可以无侵入的方式自己进行实现接口供dubbo使用。
ExtensionLoader类内部保存着所有接口的实现类,并记录了缺省Adaptive实现类和缺省的扩展类,我们可以通过属性大致了解ExtensionLoader的功能。下面是实例属性:
```
private final Class<?> type; // ExtensionLoader 创建时指定的类型
private final ExtensionFactory objectFactory; // ExtensionLoader的扩展工厂
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>(); // 记录了实现类的class与名称的对应关系
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();// 保存map,内部保存了实现类name与实现类class的对应关系
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); //保存有@Activate注解的实现类
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); //保存实现类name与实例
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); //缺省的实例
private volatile Class<?> cachedAdaptiveClass = null; // 缺省的实现类
private String cachedDefaultName; // 默认的实现类名称,@SPI中指定
private volatile Throwable createAdaptiveInstanceError; // 创建是的错误
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
```
除此之外,ExtensionLoader类内部的静态属性保存所有扩展类与ExtensionLoader、实例的映射。
```
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
```
**加载过程**
首先根据class从EXTENSION_LOADERS查找对应的ExtensionLoader,如果为null则创建一个ExtensionLoader,指定其type为要查找的class。此时虽然获取了ExtensionLoader,但还未查找接口的实现类,调用getExtension()或getAdaptiveExtension()等方法时才会真正的查找;getExtensionClasses()方法会依次查找下面3个路径的文件,读取内容获得`name=class`键值对,并保存到相应的属性中。
```
META-INF/services/
META-INF/dubbo/
META-INF/dubbo/internal/
```
下面是解析文件并保存的过程:
* cachedDefaultName 如果ExtensionLoader的type有SPI注解且设置了value,那么这个value就是该接口默认的实现类名称cachedDefaultName,例如`Compiler`接口使用了注解`@SPI("javassist")`,那么Compiler的ExtensionLoader的cachedDefaultName为javassist。cachedDefaultName的目的是getDefaultExtension()获取默认的实现类,或查找不到@Adaptiv的实现类时,使用cachedDefaultName创建Adaptive的实现类。
* 按照顺序从3个文件夹中,读取以type的全名命名的文件,解析name和class,如果实现类有@Adaptive注解,设置cachedAdaptiveClass为该实现类;
* 如果没有@Adaptive,首先判断实现类是否有将要查找的接口作为参数的构造方法,如果有添加到cachedWrapperClasses变量中,
* 如果没有这种构造方法,判断是否有@Activate注解,有的话保存到cachedActivates中;cachedNames保存所有的class和name的映射关系
* 最终,将name与实现类class的映射保存在cachedClasses中。
至此,ExtensionLoader针对给的的接口,查找了所有的扩展,并取得了默认实现类的名称,Adaptive实现类class(可能没有,后续获取是dubbo会创建),以及name与实现类class的映射关系。
有了这些信息,在createExtension(name)时就会根据name获得class然后newInstance获取实例。
## dubbo扩展测试
我们自己实现一个MyCompiler,其实现了Compiler接口
```
public class MyCompiler implements Compiler {
public Class<?> compile(String code, ClassLoader classLoader) {
return new JavassistCompiler().compile(code, classLoader);
}
}
```
在/META-INF/services/com.alibaba.dubbo.common.compiler.Compiler文件中加入下面的映射
```
mycp=com.shisj.study.dubbo.loader.MyCompiler
```
测试中,可以通过mycp获取到实现类MyCompiler
```
Compiler compile2 = loader1.getExtension("mycp");
System.out.println(compile2.getClass());//class com.shisj.study.dubbo.loader.MyCompiler
```
**getAdaptiveExtension内部逻辑**
getAdaptiveExtension首先会查找接口具有@Adaptive注解的实现类作为Adaptive类,如果没有dubbo会动态创建一个新的类,内部会从url取参数,如果没有则取默认的值,来自于接口的@SPI注解,如`@SPI("javassist")`的值。
**getExtension()**
获得Adaptive类的实例后,会将其传入cachedWrapperClasses,包装为Wrapper类