dubbo
一直使用dubbo 却没有仔细研读 dubbo的代码 所以打算近期一段时间抽空研究一下 dubbo的源码。
学习dubbo之前需要掌握的基础知识
JDK的spi
java 多线程、线程池
Netty相关的基础知识
Zookeeper基础知识,zkClient的api
设计模式,如工厂模式、装饰模式、模板模式、单例模式
dubbo 扩展点机制
先从dubbo扩展点开始 因为dubbo中四处都在用 看不懂这个 dubbo没法看
java spi
面向接口编程中,我们会根据不同的业务抽象出不同的接口,然后根据不同的业务实现建立不同规则的类,因此一个接口会实现多个实现类,在具体调用过程中,指定对应的实现类,当业务发生变化时会导致新增一个新的实现类,亦或是导致已经存在的类过时,就需要对调用的代码进行变更,具有一定的侵入性。
举个例子:
/**
* @author LDZ
* @date 2019/5/25 2:48 PM
*/
public interface api {
// 输出
void shout();
}
// 早起业务有两个实现类 一个 cat 一个 dog 两个实现类:
// dog
/**
* @author LDZ
* @date 2019/5/25 2:49 PM
*/
public class Dog implements api {
@Override
public void shout() {
System.out.println("wang wang wang");
}
}
// cat
/**
* @author LDZ
* @date 2019/5/25 2:49 PM
*/
public class Cat implements api {
@Override
public void shout() {
System.out.println("miao miao miao");
}
}
// 调用方
public class ApiClient {
private final static Map<String,api> API_LIST = new HashMap<> ();
static {
API_LIST.put ( Dog.class.getSimpleName(), new Dog ());
API_LIST.put ( Cat.class.getSimpleName(), new Cat ());
}
public void doInvoke(String apiType) {
api a = ApiClient.API_LIST.get (apiType);
if (a != null) {
a.shout();
}
}
}
如果未来业务发生了变化,多了一个 Pig 就要新增 Pig实现类,同时要修改ApiClient的static中的API_LIST 代码块,如果需要多个实现进行处理,就需要更加复杂的维护性。因此 java spi解决了调用者对实现的依赖关系,能够在配置文件中对实现进行动态的替换、添加(组件)。
java SPI 当服务的提供者,提供服务接口的一种实现后,在jar包中的 META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件就是该接口服务的具体实现类。当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。
沿用上边的例子:
path = /resources/META-INF/services/com.yi23.common.api
com.yi23.common.util.Cat
com.yi23.common.util.Dog
public class SpiApiClient {
private final static Map<String,api> API_LIST = new HashMap<> ();
static {
// 使用ServiceLoader
ServiceLoader<api> serviceLoader = ServiceLoader.load (api.class);
Iterator<api> iterator = serviceLoader.iterator ();
while (iterator.hasNext ()) {
api itr = iterator.next ();
API_LIST.put ( itr.getClass().getSimpleName(), itr);
}
}
/**
* 描述:调用相关协议
* @since JDK 1.8
*/
public void doInvoke(String apiType) {
api a = ApiClient.API_LIST.get (apiType);
if (a != null) {
a.shout ();
}
}
}
可以看到利用SPI 未来增加实现,或者替换已存在实现,只要修改 配置文件 /resources/META-INF/services/com.yi23.common.api
就可以了
dubbo扩展点和JDK spi 的区别
JDK 中的 SPI 有如下缺陷 :
- 需要调用服务时必须加载所有扩展类并生成对象,效率低
- 扩展类没有标志可以进行有效的区分。调用方需要调用某一个扩展类时无法有效指定。
Dubbo针对上述缺陷 对 serviceLoader进行了改写:
- dubbo 用到了大量的全局缓存 所有Extension都缓存在chachedInstances中,该对象类型是ConcurrentMap<Stirng, Holder>
- 可以动态获取扩展对象
- Dubbo 扩展点提供了 AOP
- Dubbo 扩展点提供了 IOC (后续分析)
源码
dubbo spi 的目的是什么? 帮我们获取一个我们所需要的指定的对象。如何获取所需的对象呢?通过调用ExtensionLoader.getExtension(String name),从这个代码段入手分析这段代码的整个流程
在Dubbo源码中大面积使用这种写法,都是获得某个接口的适配类,在真正执行的时候才决定最终的作用类
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();`
首先看一下 Protocol.class
到底是什么
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*/
SPI("dubbo")
public interface Protocol {
/**
* 获取默认端口
*
* @return default port
*/
int getDefaultPort();
/**
* 暴露远程服务
* 1. Protocol 应该在收到请求后记录请求源地址
* RpcContext.getContext().setRemoteAddress();
* 2. export() 必须是幂等的, 也就是说, 当暴露相同的URL时候,调用一次和两次是没有区别的
* 3. 调用方实例由框架传入,协议不需要关心
*
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* 引用远程服务
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
/**
* 释放协议: <br>
*/
void destroy();
}
这是一个协议接口 类上有一个 @SPI
注解(默认值 “dubbo”), 在方法上有 @Adaptive
注解
这个 SPI
注解来标识为spi接口,其中value值为默认扩展类标识:
package com.ryan;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* 默认扩展类标识
*/
String value() default "";
}
继续分析 getExtensionLoader()
方法。
// 因为每一个扩展类加载器只能加载特定的SPI扩展的实现,所以要获得某个扩展的实现的话首先要找到他对应的扩展类加载器(ExtensionLoader) 一个扩展接口的所有实现都是被同一个扩展类加载器来加载的
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// 要有 @SPI 注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 先从缓存上取值,如果没有,就去new一个新的,然后放进缓存
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 的构造方法
private ExtensionLoader(Class<?> type) {
// 给 type 赋值, 给objectFactory 赋值
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
对于 ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
, 此时传入的type是Protcol.class, 继续执行 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
此时分为两个步骤分析:
ExtensionLoader.getExtensionLoader(ExtensionFactory.class)
此时 type 是ExtensionFactory.class
得到一个ExtensionLoader
实例 此实例中objectFactory
是null
getAdaptiveExtension()
为cachedAdaptiveInstance
赋值
首先看 getAdaptiveExtension()
方法
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
// 单例双重检查 如果没有则进入`createAdaptiveExtension()`方法
//cachedAdaptiveInstance作为一个Holder(只有简单的get和set方法),也是一个锁对象
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);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
createAdaptiveExtension()
方法分两部分 一个是injectExtension
一个是 getAdaptiveExtensionClass().newInstance()
// 创建一个接口的适配类
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
//获取 AdaptiveExtensionClass 并完成注入
//基本分两步:
// 1.获取适配器类 getAdaptiveExtensionClass().newInstance()
// 2.在适配器里面注入其他的扩展点 injectExtension
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
getAdaptiveExtensionClass()
,获取一个适配器扩展点的类
// 获得一个 适配器扩展点
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
// 双重检查锁判断 防止同一个扩展点被多次加载
// 加载当前扩展所有实现,看是否有实现类上被标注为@Adaptive
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// loadExtensionClasses 会加载所有的配置文件,将配置文件中对应的的类加载到当前的缓存中
// load完之后该classes已经保留了所有的扩展类映射关系
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
// loadExtensionClasses() 方法
// 此方法已经getExtensionClasses方法同步过
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
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;
}
这里有个type.getAnnotation(SPI.class),这个type就是刚刚再初始化ExtensionLoader的时候传入的,我们先看type=ExtensionFactory.class的情况,ExtensionFactory接口类上有@SPI注解,但是value为空,然后三次调用loadFile方法,分别对应Dubbo扩展点的三个配置文件路径,在源码中我们可以找到ExtensionFactory对应的文件,如下:
path=dubbo-common/target/classes/META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
path = META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactor
通过 loadDirectory() 方法,最终extensionClasses
返回SpringExtensionFactory
和SpiExtensionFactory
缓存到cachedClasses
中。AdaptiveExtensionFactory
因为类上有@Adaptive
注解,所以直接缓存到cachedAdaptiveClass
中。
至此 我们拿到了 extensionClasses
, 并将其缓存到了cachedClasses中, 重新进入createAdaptiveExtensionClass()
方法,这个方法的目的是自动生成和编译一个动态代理适配器类,名字叫Protocol$Adaptive
, 这里又用到了一个Compile
扩展点,可以看到,这里用到了ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension()
执行`complier.compile(code, classLoader) 如下:
其中DEFAULT_COMPILER
值为 JavassistCompiler
,执行loader.getExtension(name)
,结果是得到JavassistCompiler
实例,这里是一个装饰模式的设计,最终调用JavassistCompiler.compile()
方法得到Protocol$Adpative
回到分析这段代码的入口:
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
其得到的返回值就是 Protocol$Adpative
这个代理类为:
package org.apache.dubbo.rpc;
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);
}
}
这时 我们得到了 Protocol的适配代理类, 最后 我们看下 injectExtension(instance)
方法
// 通过反射自动调用instance的set方法把自身的属性注入进去,解决的扩展类依赖问题,也就是说解决扩展类依赖扩展类的问题
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 1.拿到所有的方法
for (Method method : instance.getClass().getMethods()) {
// 判断是不是 set 方法
if (isSetter(method)) {
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 得到属性名称,比如setName方法就得到name属性名称
String property = getSetterProperty(method);
// 从objectFactory中获取所需要注入的实例
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
至此 dubbo 完成了依赖注入,关于 dubbo 的扩展点机制的源代码 基本分析完成了
总结
1.为了获得一个扩展点的适配类,首先会看缓存中有没有已经加载过的适配类,如果有的话就直接返回,没有的话就进入第2步。
2.加载所有的配置文件,将所有的配置类都load进内存并且在ExtensionLoader内部做好缓存,如果配置的文件中有适配类就缓存起来,如果没有适配类就自行通过代码自行创建适配类并且缓存起来(代码之后给出样例)。
3.在加载配置文件的时候,会依次将包装类,自激活的类都进行缓存。
4.将获取完适配类时候,如果适配类的set方法对应的属性也是扩展点话,会依次注入对应的属性的适配类(循环进行)。