Dubbo内核之SPI(1)
SPI是什么
SPI全程Service Provider Interface,是java提供的用来被第三方实现或者扩展的API,可以用来启用框架扩展或者替换组件
使用场景
比较常见的例子:
- 数据库驱动加载接口实现类
- 日志门面接口实现类加载
- dubbo中SPI的利用
使用介绍
- 编写标准服务接口,并且服务提供者实现对应接口
public interface HelloService {
void sayHello();
}
public class AHelloServiceImpl implements HelloService{
@Override
public void sayHello() {
System.out.println("A say hello");
}
}
public class BHelloServiceImpl implements HelloService{
@Override
public void sayHello() {
System.out.println("B say Hello");
}
}
- 在META-INF下面新增一个文件夹services,在路径下新建一个名称为接口全限定路径名的文件,至于为什么是META-INF/servies,因为通过ServiceLoader类里面写死了路径,如下图:
文件中的内容为接口具体实现类的全限定路径名:
com.loup.dubbo.test.spi.AHelloServiceImpl
com.loup.dubbo.test.spi.BHelloServiceImpl
- 通过ServiceLoader.load()去加载对应接口的实现类,ServiceLoader实现了Iterable,所以是可迭代循环的,而且ServiceLoader也是通过循环去加载所有的资源文件,最后执行接口如下:
Dubbo中SPI的实现
dubbo中的SPI对于Java中原生的SPI机制做了优化,接口文件中使用key-value的形式,dubbo不仅只加载META-INF/services下面的文件,还有META-INF/dubbo和META-INF/dubbo/internal/。dubbo中加载是用ExtensionLoader
dubbo对java的SPI做了改进优化
- JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
- java中如果扩展点加载失败,连扩展点的名称都拿不到了
ExtensionLoader
- ExtensionLoder.getExtensionLoader(Class type); ExtensionLoader.getExtensionLoader(Protocol.class)
每个有SPI注解的接口使用ExtensionLoder加载,都会创建ExtensionLoader实例,放在ConcurrentMap<Class, ExtensionLoader> EXTENSION_LOADERS缓存中
扩展点
静态扩展点
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Protocol.class);
loader.getExtension("dubbo");
- loader.getExtension("dubbo");会加载到META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol文件,里面内容是dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol,key为dubbo,value为接口的实现类全路径名称,加载完后以dubbo名称为key放在map缓存中,然后通过get(name)获取类,然后反射生成对应接口实现类的实例,放在EXTENSION_INSTANCES缓存中,方法大致流程调用如下:
自适应扩展点
loader.getAdaptiveExtension();
通过上面getAdaptiveExtension获取自适应默认扩展点,自适应扩展点有两种,一种是方法级别的,一种是类级别的
类级别的自适应
dubbo中类级别的自适应不多,据我所知就两个AdaptiveExtensionFactory和AdaptiveCompiler,如下Compiler获取自适应扩展点
ExtensionLoader.getExtensionLoader(Compiler.clss).getAdaptiveExtension();
public T getAdaptiveExtension() {
......
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 此方法中获取自适应的实例
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
// injectExtension方法暂时不看,此方法时依赖注入用的,因为类级别的自适应是手动创建的类,不排除其中有依赖其他扩展点
// 此刻主要关注的是getAdaptiveExtensionClass方法,会通过反射生成对应类的实例,方法中会调用getExtensionClasses,主要逻辑还是要看getExtensionClasses方法
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
// 最后还是回到loadExtensionClasses方法,加载配置文件中的接口对应实现类,加载的时候会判断接口实现类是否有类级别的@Adaptive注解,如果有,则把当前类缓存到cachedAdaptiveClass中,getAdaptiveExtensionClass方法会判断此字段是否为空
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
方法级别
ExtensionLoader.getExtensionLoader(Protocol.clss).getAdaptiveExtension();
基本逻辑跟方法级别一样,只是方法级别的读取配置文件时,没有类有注解@Adaptive注解,所以getAdaptiveExtensionClass中判断的cachedAdaptiveClass就为空,走到createAdaptiveExtensionClass()
private Class<?> createAdaptiveExtensionClass() {
// 此处生成一个XX$Adaptive的自适应类
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
// Protocol.class接口中export()和refer()方法有@Adaptive注解,dubbo生成的类如下:
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public java.util.List getServers() {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}
其实借鉴dubbo中此种处理方法,代码中要是if-else过多,可以采用此种方法改造,当然使用策略+工厂模式也是中方法。看源码看的是其中思想。