概念
dubbo spi是dubbo对JDK spi的升级,针对JDK spi的一些弱势进行了优化,官网的介绍如下:
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本>时,会报不支持 ruby,而不是真正失败的原因。
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
附上链接:https://dubbo.incubator.apache.org/zh-cn/docs/dev/SPI.html
dubbo一方面用spi机制完成自己的默认实现,同时允许开发者在不清楚dubbo代码细节的同时根据自己需求对框架能力进行拓展。模仿该设计方式,可以让很多流程性业务编码变得非常简洁。
环境搭建
从来不想在搭环境上浪费太多时间,用最简单的方式,免得降低读代码的耐心,
不需要构建spring应用,普通工程即可,在pom中添加依赖:
com.alibaba
dubbo
2.6.6
io.netty
netty-all
4.1.36.Final
redis.clients
jedis
3.0.1
dubbo版本使用2.6.6,后面源码介绍也是基于这个版本的,使用redis注册中心。
接口以及其实现类:
public interface DemoService {
String sayHello(String name);
}
public class DemoServiceImpl implements DemoService{
public String sayHello(String name) {
return "Hello " + name;
}
}
参考官方文档,编写测试代码:
// 服务实现
DemoService demoService = new DemoServiceImpl();
// 当前应用配置
ApplicationConfig application = new ApplicationConfig();
application.setName("dubbo-learn");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setProtocol("redis");
registry.setAddress("127.0.0.1:6379");
// 服务提供者协议配置
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("dubbo");
protocol.setPort(12345);
protocol.setThreads(200);
// 服务提供者暴露服务配置
// 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
ServiceConfig service = new ServiceConfig();
service.setApplication(application);
// 多个注册中心可以用setRegistries()
service.setRegistry(registry);
// 多个协议可以用setProtocols()
service.setProtocol(protocol);
service.setInterface(DemoService.class);
service.setRef(demoService);
service.setVersion("1.0.0");
// service.setFilter("demoFilter");
// 暴露及注册服务
service.export();
// 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
ReferenceConfig reference = new ReferenceConfig();
reference.setApplication(application);
// 多个注册中心可以用setRegistries()
reference.setRegistry(registry);
reference.setInterface(DemoService.class);
reference.setVersion("1.0.0");
// 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
DemoService demoService1 = reference.get();
System.out.println(demoService1.sayHello("jules"));
启动redis,就可以执行测试代码了,效果如图
我们顺便看下服务暴露以后,redis中保存的服务信息
可以看到注册信息在redis是以哈希表的方式保存,哈希表的key即服务URL信息
源码
初始化
在阅读dubbo代码时,经常看到如下实现:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
dubbo使用ExtensionLoader实现他的spi机制,以上代码会生成一个Protocol的动态代理实现,由代理去执行真实逻辑。
每个扩展点对应一个ExtensionLoader,getExtensionLoader(Protocol.class)实际为获取Protocol.class的ExtensionLoader,代码如下:
public static ExtensionLoader getExtensionLoader(Class type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
先从缓存中获取,获取不到则创建并放入缓存中,以供下次使用,ExtensionLoader只有一个构造方法:
private ExtensionLoader(Class> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
获取ExtensionLoader以后,执行getAdaptiveExtension()方法获取动态生成的Protocol,我们阅读getAdaptiveExtension的代码逻辑:
public T getAdaptiveExtension() {
// 从缓存中读取
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
// 检查生成的时候是否出现异常
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
// 典型的单例生成方式,两次验证
if (instance == null) {
try {
// 生成单例并且缓存起来
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
以上代码可知,核心逻辑在createAdaptiveExtension中,进一步阅读:
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
代理类在getAdaptiveExtensionClass中生成,然后newInstance生成对象注入到injectExtension方法,于是先阅读Class的创建过程,getAdaptiveExtensionClass的代码为:
private Class> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getExtensionClasses()篇幅比较长,主要是在第一次启用的时候,加载所有的扩展信息,getExtensionClasses调用loadExtensionClasses,我们直接看loadExtensionClasses的代码:
private Map> loadExtensionClasses() {
// 读取扩展点上的value值,放入cachedDefaultName中
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map> extensionClasses = new HashMap>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
dubbo的spi注解有一个String的value对象,以上代码会读取value值并存入cachedDefaultName中,可以看下Protocol的spi注解value给的值是dubbo,申明了默认的实现
然后,连续调用了三个loadDirectory方法,三个DIRECTORY分别是:
- META-INF/dubbo/internal/
- META-INF/dubbo/
- META-INF/services/
其中META-INF/dubbo/internal/里面都是dubbo内部的一些扩展实现,官网在介绍spi机制的时候约定的路径是META-INF/dubbo/,理论上META-INF/services/也支持。
打开/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件,里面有dubbo实现的对扩展点Protocol的实现:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper
Protocol的默认实现类是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol。
loadDirectory调用loadResource,读取/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件并且一行一行遍历,获取实现类的class对象,然后在loadResource中会调用loadClass方法:
private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException {
// 处理Adaptive注解并且放到cachedAdaptiveClass中
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());
}
// 包装类,则放入wrappers中
} else if (isWrapperClass(clazz)) {
Set> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
// 未带注解的实现类,放入extensionClasses和cachedNames中
clazz.getConstructor();
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
这里引出了两个个非常重要的注解:Adaptive和Activate,具体作用我们后面再说,阅读上面代码的第一个if可以确认,dubbo只允许一个实现类上出现Adaptive注解,否则会出现IllegalStateException异常。
总结一下上面的代码,遍历/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件,获取到内部所有的class对象,会遇到四种情况:
类别 | 处理 | 备注 |
---|---|---|
实现类并且带了Adaptive注解 | 放到cachedAdaptiveClass | 只允许有一个带有该注解 |
实现类并且不带Adaptive注解 | 放到cachedActivates和cachedNames | - |
实现类并且带Activate注解和Adaptive注解 | 放到cachedActivates和cachedNames的同时,放入cachedActivates | - |
包装类 | 放到cachedWrapperClasses | - |
我们现在回到getAdaptiveExtensionClass方法,避免去翻,在附上一次代码:
private Class> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
通过阅读,已经了解getExtensionClasses方法主要是对扩展文件的解析和实现类的加载,其实也是为我们后续的调用做好准备,加载需要做IO,好在每个ExtensionLoader只需要执行一次具体的加载逻辑就行,初始化的时候已经准备就绪了。
接下来也是核心部分,也就是createAdaptiveExtensionClass方法,真正去创建Protocol代理实现类的方法:
private Class> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class)
.getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
第一行调用createAdaptiveExtensionClassCode生成code,createAdaptiveExtensionClassCode方法篇幅很长,不再赘述了,功能是生成动态类的类编码,Protocol会生成如下代码:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"
);
}
public int getDefaultPort() {
throw new UnsupportedOperationException(
"method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"
);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba
.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Exporter export (com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
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);
}
}
可以看到,代理类实现扩展点Protocol的所有实现,其中对export和refer进行了实现,而getDefaultPort和destroy会直接异常,原因是Protocol的export和refer方法带有注解@Adaptive,在createAdaptiveExtensionClassCode的逻辑中,只对有@Adaptive修饰的方法进行实现,@Adaptive可以注解在方法或者类上,注解在方法上则会动态生成实现方法,在该方法中会去查找真实需要执行逻辑的实现类(dubbo自带的或者是开发者扩展的)。
在Protocol$Adaptive中通过
com.alibaba.dubbo.rpc.Protocol extension ExtensionLoader.getExtensionLoader(
com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
获取到一个Protocol的实现类,然后调用这个实现类对应的方法,那么我们可以判断,只有在发生真实调用的时候,我们才可以判断具体是执行了哪个实现类的对应方法。
最后,用对应的classLoader实例化Protocol$Adaptive,初始化过程就完成了。
调用
接下来看下怎么使用这个代理类吧,在Dubbo官网提供的分层模型中,Protocol位于远程调用层,主要封装了RPC调用,其他几个很重要的概念:Invoker,Invoker也在这一层,劝退重灾区。我们以protocol的refer进行探索,refer方法会生成Invoker,基于不同的协议会生成不同的Invoker。
可以直接在com.alibaba.dubbo.config.ReferenceConfig#createProxy中进行断点,Proxy初始化需要Invoker,Invoker的生成逻辑入口在createProxy中。
截取createProxy中的部分代码
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
List us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config.");
}
}
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List> invokers = new ArrayList>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
这边啰嗦下,目前的版本中,本地服务默认就是本地调用了,也就是如果你的服务提供者和你的服务发起者都在本地,默认是使用本地服务,以上代码中isJvmRefer会等于true,我在自己阅读的时候,一般直接回在debug的时候去修改isJvmRefer的值以生成远程调用的Invoker,这次的关注点不在Invoker,我们关心的是refprotocol.refer(interfaceClass, url)在执行的时候,是怎么找到对应的实现类的。
由之前的分析得知,这边的refprotocol.refer其实是调用了Protocol$Adaptive.refer方法(我一直不知道怎么对动态生成的类进行debug,是不是完全不行呢?),代码是:
public Invoker refer(Class arg0, URL arg1) throws RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
Protocol extension = (Protocol) ExtensionLoader.getExtensionLoader(
Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
这边有两行重要的代码
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
根据URL信息(dubbo的协议都是存在url里面的)信息,获取到Protocol,如果是null,就默认使用dubbo,否则使用URL的配置,翻回去看下META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件的内容,每个extName其实就是对应的Protocol的实现。
我们回到ExtensionLoader,开始阅读getExtension的代码
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder
cachedInstances用来缓存对应的实现,第一次进来肯定是空的,dubbo只有在真正需要某个实现类的时候才去实例化它,所以不需要担心会有不使用但是很耗时的类被实例化。然后执行createExtension(name)实例化扩展对象。
private T createExtension(String name) {
// 从缓存中拿到对应的Class,在加载配置文件的时候已经加入缓存
Class> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(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> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
实例化或者从缓存中拿到对应的实例,injectExtension会进行简单的依赖注入,然后返回实例,这时我们返回的参是真实要去执行refer方法的Protocol实现,本地服务就是InjvmProtocol。
至此,ExtensionLoader初始化 到执行这个流程就结束了。