上篇文章整体介绍了Dubbo的架构,其中也提到了,学好Dubbo的关键在于先整明白SPI原理,今天就带大家一起学习下相关设计
一、Java SPI
在正式解读Dubbo SPI之前,先简单带大家过下Java原生的SPI。
1.定于接口
public interface Rpc {
void invoke(String method);
}
2.定义实现类
public class Dubbo implements Rpc {
@Override
public void invoke(String method) {
System.out.println("Dubbo" + "_" + method);
}
}
public class Thrift implements Rpc {
@Override
public void invoke(String method) {
System.out.println("Thrift" + "_" + method);
}
}
3.定义SPI声明
在项目的Resources目录下,定于SPI声明文件
4.调用SPI
可以发现,RPC对应的两个实现类,此时都已经被加载到内存里。
5.实现原理
优点
- 解藕,应用程序可以根据实现业务情况启用或替换组件 缺点
- 加载实现类的时候会全部加载,不管你是否会不会用到
- 只能通过迭代器获取,无法通过关键字获取
- 非线程安全,多线程情况下可能会有线程安全问题
- 不会有好的提示扩展点加载失败异常
二、Dubbo增强SPI
Dubbo扩展点机制是基于JDK标准SPI扩展机制增强而来,解决了JDK SPI以下问题
- 不用一次性加载所有扩展点实现
- 如扩展点加载失败,不会有好的提示异常
- 增加了对扩展点IOC和AOP的支持,一个扩展点可以通过setter方法注入其他扩展点
1.服务提供者
public class Application {
public static void main(String[] args) throws Exception {
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap
.application(new ApplicationConfig("dubbo-demo-api-provider"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.service(service)
.start()
.await();
}
}
2.扩展点切入
以Protocol为例,服务发布者通过ServiceConfig进行服务的发布,其内部会通过以下方法获取Protocol实例
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
查看Protocol代码发现,其明显是个扩展点,默认注入实现为“dubbo”
@SPI("dubbo")
public interface Protocol {
... ...
}
3.ExtensionLoader分析
ExtensionLoader类似JDK的ServiceLoader,作用也是获取到对应的SPI实现类,不过这里getAdaptiveExtension获取到的最终是Protocol的适配类,后面会讲是怎么动态生成的。
- getExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
...省略校验 ...
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
可以看到,在ExtensionLoader中,每个扩展接口,都会对应一个ExtensionLoader对象,内部通过一个map进行缓存。
- getAdaptiveExtension
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
... 省略校验 ...
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
... 异常处理 ...
}
}
}
}
return (T) instance;
}
可以看到,先从内部的cache中获取适配类,如果不存在,则调用createAdaptiveExtension方法进行创建。
- createAdaptiveExtension
private T createAdaptiveExtension() {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}
首先调用getAdaptiveExtensionClass.newInstance()方法获取到适配器class,然后调用injectExtension进行扩展点相互依赖注入
- getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
private Class<?> createAdaptiveExtensionClass() {
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);
}
首先调用getExtensionClasses获取该接口的所有实现类,然后调用createAdaptiveExtensionClass方法创建具体的适配器对象
- getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
// 拿Protocol来说,这里的cacheDefaultName就是dubbo
cacheDefaultExtensionName();
// 然后去 META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal目录下去加载具体的实现类
Map<String, Class<?>> extensionClasses = new HashMap<>();
// internal extension load from ExtensionLoader's ClassLoader first
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
首先获取到cacheDefaultName,然后分别去META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal目录下去加载具体的实现类
这里可以看下Dubbo的注入SPI文件格式,和Java原生的其实也有区别。
- createAdaptiveExtensionClass
private Class<?> createAdaptiveExtensionClass() {
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);
}
通过字节码生成适配器class对象并返回,然后通过newInstance方法生成对应实例
可以看到最终生成的是“Protocal$Adaptive”类型的对象(暂时不展开)
- injectExtension
private T injectExtension(T instance) {
try {
for (Method method : instance.getClass().getMethods()) {
... 省略校验...
try {
// 根据setter属性获取对应的属性名,比如getUser,则获取到的user
String property = getSetterProperty(method);
// 该属性是否是扩展点实现
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// setter注入
method.invoke(instance, object);
}
} catch (Exception e) {
}
}
} catch (Exception e) {
}
return instance;
}
进行扩展相互依赖属性注入
- getExtension
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
根据扩展类的名称,找到对应的实现类
4.小结
以上就是整体的调用流程,涉及到的方法调用比较多
核心点在于,Dubbo会从以下四个目录读取文件
- META-INF/dubbo/internal/
- META-INF/dubbo/
- META-INF/services/
- META-INF/dubbo/external/
文件名为接口的全限定名,内容为键值对,键为短名称(即SPI注解里写的字符串,如“dubbo”),值为实现类。
读取到对应的配置后,会创建对应的SPI类,同时如果关联了多个Wrapper类,也会一一生成。最后生成动态“Protocal$Adaptive”适配对象。
当我们代码中调用protocal.export的时候,在没有扩展点包装的时候,调用的就是DubboProtocol的export对象 。
但实际使用过程中,会使用ProtocolFilterWrapper、ProtocolListenerWrapper等Wrapper类对DubboProtocol对象进行增强,下面就让我们看看,Wrapper类是如何进行增强的。
三、扩展点自动包装
查看ProtocolListenerWrapper源码,protocol实际类型为DubboProtocol。
public class ProtocolListenerWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
... ...
}
第二次使用ProtocolFilterWrapper对ProtocolListenerWrapper进行封装,protocol为ProtocolListenerWrapper
1 包装类收集
回到上一讲,getExtensionClasses里会调用loadDirectory加载扩展接口的所有实现类对象,同时会对包装类Wrapper也进行了收集,让我们看下loadDirectory -> loadDirectory -> loadClass方法
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
... ...
}
}
加载类的时候,会调用isWrapperClass方法对clazz进行判断,如果当前是Wrapper类 ,则会进行缓存收集
private boolean isWrapperClass(Class<?> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
通过判断当前clazz构造函数是否包含当前被加载的类,来判断clazz是否是包装类。那么对于缓存的Wrapper类是如何使用的呢?
2 包装类使用
private T createExtension(String name) {
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
... ...
}
}
在调用getExtension获取对应的实现类时,底层会调用createExtension来创建的实现类 ,如果当前存在Wrapper类的话,则会遍历使用injectExtension一层一层对扩展类进行增强。
debug也可以看到,最终Protocol对应两个增强类,先由ProtocolListenerWrapper对DubboProtocol进行增强,然后使用ProtocolFilterWrapper对ProtocolListenerWrapper进行增强。
四、Dubbo动态编译
1.Adaptive实现
前文也提到过,在获取到Protocol的SPI实现类DubbolProtocol后,Dubbo会生成一个Protocol$Adaptive对象,其作用实际上是个适配器,封装了Protocol的具体实现类,查看源码如下(可以通过阿里的Arthas来查看)
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
... ...
// 根据extName找到具体适应类,然后调用方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}
内部调用ExtensionLoader对应方法,获取到SPI具体实现,其实获取到的就是增强后的类,然后执行其export方法进行服务引用
2.适配器原理
Dubbo提供了Compiler接口,用来实现动态编译功能,其本身也是个SPI。提供了两种实现,分别是默认实现JavassistCompiler以及JdkCompiler
@SPI("javassist")
public interface Compiler {
Class<?> compile(String code, ClassLoader classLoader);
}
再次回顾前文的动态创建方法
private Class<?> createAdaptiveExtensionClass() {
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);
}
可以看到,也是通过ExtensionLoader来加载Compiler的具体SPI实现,然后动态的生成代理类。所以,Compiler类一定是最先加载的,然后才能去创建其他的适配器。
五、小节
本篇带大家回顾了Java原生SPI的工作原理及缺点,同时分析了Dubbo的SPI流程以及动态加载原理,如果看到这觉得有收获的话,不妨点个赞,有什么问题也可以在评论区讨论交流~