扩展点机制

扩展点的配置

(1)根据关键字读取配置,获取具体的实现类

比如在 dubbo-demo-provider.xml 文件中配置:

<dubbo:service protocol="rmi" interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />

则会根据rmi去读取具体的协议实现类RmiProtocol.java 。

(2)@SPI和@Adaptive

  • @SPI注解,可以认为是定义默认的实现类;
  • @Adaptive注解:该注解打在接口的方法上;调用ExtensionLoader.getAdaptiveExtension()获取适配类,会先通过前面的过程生成 java 的源代码,在通过编译器编译成 class 加载。

扩展点加载流程

以如下例子为例:

private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

在这个语句中,我来分析,首先 Protocol 类带有 SPI 注解,因此我们可以确认默认是使用 ***dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol ***作为默认扩展点。

@SPI("dubbo")
public interface Protocol {
/**
* 获取缺省端口,当用户没有配置端口时使用。 *
*
* @return 缺省端口
*/
int getDefaultPort(); /**
* 暴露远程服务:<br>
* 1. 协议在接收请求时,应记录请求来源方地址信息: RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没 有区别。<br>
* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br> *
*
* @param <T> 服务的类型
* @param invoker 服务的执行体
* @return exporter 暴露服务的引用,用于取消暴露
* @throws RpcException 当暴露服务出错时抛出,比如端口已占用
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* 引用远程服务:<br>
* 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行
同URL远端export()传入的Invoker对象的invoke()方法。<br>
* 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请
求。<br>
* 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br> *
*
*
*
*
*
*/
@param <T>服务的类型
@param
type 服务的类型
@param
url 远程服务的URL地址
@return
invoker 服务的本地代理
@throws
RpcException 当连接服务
供方失败时抛出
@Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; /**
* 释放协议:<br>
* 1. 取消该协议所有已经暴露和引用的服务。<br>
* 2. 释放协议所占用的所有资源,比如连接和端口。<br> * 3. 协议在释放后,依然能暴露和引用新的服务。<br>
*/
void destroy();
}

这里对应的函数为:

@SuppressWarnings("unchecked")
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 函数:

@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T)
getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
} private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

动态生成类

而 cachedAdaptiveInstance 类则是若有 cachedAdaptiveClass 对象,则直接返回,否则通 过生成类文件,然后 complier 出来的,其文件内容如下:从该文件可以很容易看出只有标 注了@Adaptive 注释的函数会在运行时动态的决定扩展点实现;

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative 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.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
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(co m.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
} public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
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(co m.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}

此时我们分析 getExtension 函数:

/**
* 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException}.
*
* @param name * @return
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(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;
}

此时我们分析 createExtension,

@SuppressWarnings("unchecked")
private T createExtension(String name) {
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, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
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 + ") couldnotbeinstantiated:" + t.getMessage(),
t);
}
}

而这里 injectExtension 类,则是为生成的 instance 注入变量; 其目标是搜索所有 set 开头,同时只有一个入参的函数,执行该函数,对变量进行注入;

private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() +
method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt,
property);
if (object != null) {
method.getName()
" + e.getMessage(), e);
}
}
}
}
} catch(Exception e){
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " +
+" of interface " + type.getName() + ":
logger.error(e.getMessage(), e);
}
return instance;
}

此时我们的目光转到如下一段代码:

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if(wrapperClasses !=null&&wrapperClasses.size()>0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T)
wrapperClass.getConstructor(type).newInstance(instance));
}
}

在分析这段代码的作用之前,我们先来分析一下Set<Class<?>> cachedWrapperClasses是如何被赋值的;

此时我们转到,private void loadFile(Map<String, Class<?>> extensionClasses, String dir) 函数:



分析:这里实际上是如果该类带有 Adaptive 注解,则认为是 cachedAdaptiveClass;若该 类没有 Adaptive 注解,则判断该类是否带有参数是 type 类型的构造函数,若有,则认为是wrapper 类;

于是我们分析如下文件 dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol 其内容为:

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper

listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper

mock=com.alibaba.dubbo.rpc.support.MockProtocol

我们分析这三个类,会发现 mock 类没有参数为 Protocol 的自定义参数,而其他两个均有; 此时我们返回到 createExtension 函数:

    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) { for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
} }

此时可以发现这里对 instance 加了装饰类;对于 Protocol 来说加了两个装饰类 ProtocolFilterWrapper 和 ProtocolListenerWrapper;

也就/injectExtension 实例化包装类,并注入接口的适配器, 注意这个地方返回的是最后一 个包装类。

扩展点装饰

Filter

Dubbo学习摘录(二)的更多相关文章

  1. Dubbo学习笔记(二) Dubbo的基本配置

    Check启动检查 根据之前的学习,我们简单理解的Dubbo远程调用的基本流程,服务提供者注册到注册中心,然后服务消费者通过监听注册中心达到远程调用的目的,那么如果注册中心中没有消费者对应的接口会怎么 ...

  2. dubbo学习总结二 服务端

    服务端主要执行对底层数据库的操作 主要分层为 api +dao+ filter+ util+... 首先 dubbo 服务端有一个dubbo配置文件 dubbo:application 定义应用名称 ...

  3. dubbo学习笔记(二)dubbo中的filter

    转:https://www.cnblogs.com/cdfive2018/p/10219730.html dubbo框架提供了filter机制的扩展点(本文基于dubbo2.6.0版本). 扩展接口 ...

  4. Dubbo学习摘录(一)

    Dubbo基于自定义配置,实现自己的Bean. 通常,完成一个自定义配置需要以下步骤: 设计配置属性和JavaBean: 编写XSD文件: 编写NamespaceHandler和BeanDefinit ...

  5. Dubbo学习摘录(零)

    节点角色说明: Provider:暴露服务的服务提供方: Consumer:调用远程服务的服务消费方: Registry:服务注册与发现的注册中心: Monitor: 统计服务的调用次数和调用时间: ...

  6. dubbo学习笔记二(服务调用)

    项目结构 代码示例 由于之前的IEchoService 的一个方法只是在服务端控制台打印,不便在浏览器测试,所以新添加的方法 api和服务端代码变更 public interface IEchoSer ...

  7. dubbo学习(二)配置dubbo XML方式配置

    provider(生产者) <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo= ...

  8. Dubbo 学习(二)服务注册与发现

    在上一篇中我们提到,dubbo官网上推荐使用ZooKeeper作为注册中心.那么今天我们就通过代码来实践一番,看看一个dubbo的服务消费者如果找到通过ZooKeeper暴露自己的dubbo服务提供者 ...

  9. Dubbo学习系列之十二(Quartz任务调度)

    Quartz词义为"石英"水晶,然后聪明的人类利用它发明了石英手表,因石英晶体在受到电流影响时,它会产生规律的振动,于是,这种时间上的规律,也被应用到了软件界,来命名了一款任务调度 ...

随机推荐

  1. P5597 【XR-4】复读

    枚举终点u,把路径(1,u)压起来(不考虑u的子树),并起来之后暴力

  2. NamedPipeStream的使用案例

    NamedPipeStream的使用具体案例如下: using System; using System.Data; using System.Data.SQLite; using System.IO ...

  3. Calcite分析 - RelTrait

    RelTrait 表示RelNode的物理属性 由RelTraitDef代表RelTrait的类型 /** * RelTrait represents the manifestation of a r ...

  4. MySQL8.0项目启动遇到的问题

    写在前面 看到jeecg论坛更新了jeecg-boot版本, 比较新颖的技术都有, down代码, 执行sql脚本, 起项目, 本来是一气呵成的事儿遇到了两个问题, 做个记录. 环境: IDEA201 ...

  5. semi-join子查询优化 -- FirstMatch策略

    FirstMatch执行semi-join子查询的一种策略. 类似于MySQL 5.x中如何执行in.exists子查询. 让我们以搜索拥有大城市的国家为例: select * from Countr ...

  6. 调用百度API接口 正解析地址和逆解析

    地址解析(结构化地址 解析得到 经纬度): public void SaveLocation(DataRequest<Location> request, DataResponse< ...

  7. 最稳定万能vip视频解析接口 支持HTTPS

    最稳定万能vip视频解析接口 支持HTTPS https://cdn.yangju.vip/k/?url=后面加上播放的地址即可 https://cdn.yangju.vip/k/?url= http ...

  8. hibernate的load和get有什么作用

    ① load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常(ObjectNotFoundException)load方法加载实体对象的时候,根据 ...

  9. 使用ffmpeg -re循环推流(循环读取视频文件)推送EasyDSS RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器的方法

    需求分析 众所周知,EasyDSS与EasyNVR最大的区别是,EasyDSS被动接受前端设备的推流,将推送过来的直播流进行直播转码.智能处理.视频分发,在通过CDN分发节点分发到终端播放SDK.而E ...

  10. python:单元测试框架pytest的一个简单例子

    之前一般做自动化测试用的是unitest框架,发现pytest同样不错,写一个例子感受一下 test_sample.py import cx_Oracle import config from sen ...