扩展点 Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等。
Wrapper Dubbo在加载某个接口的扩展类时候,如果某个实现中有一个拷贝类构造函数,那么该接口实现就是该接口的包装类,此时Dubbo会在真正的实现类上层包装上盖Wrapper。即这个时候从ExtensionLoader中返回的实际扩展类是被Wrapper包装的接口实现类。
Adaptive 这个自适应的扩展点比较难理解,所以这里直接以一个例子来讲解:在RegistryProtocol中有一个属性为Cluster,其中Protocol和Cluster都是Dubbo提供的扩展点,所以这时候当我们真正在操作中使用cluster的时候究竟使用的哪一个cluster的实现类呢?是FailbackCluster还是FailoverCluster?Dubbo在加载一个扩展点的时候如果发现其成员变量也是一个扩展点并且有相关的set方法,就会在这时候将该扩展点设置为一个自适应的扩展点,自适应扩展点(Adaptive)会在真正使用的时候从URL中获取相关参数,来调用真正的扩展点实现类。具体的实现会在下面的源码中详细解释。对于Adaptive的理解其实个人推荐的是Dubbo开发者指南,指南中有对于Adaptive的明确介绍
Activate 官网的叫法是自激活,其实这个更合适的叫法我认为是条件激活,我们还记得上一篇中有提到Filter的内容,其中Filter链的获取就是通过@Activate注解来确定的,所以Activate的作用主要是:提供一种选择性激活的条件,可以是我们通过相关的配置来确定激活哪些功能
1、java spi不能单独获得某个指定的实现类
比如,无法再配置文件里写入两个实现类,无法定位你要的某一个,只能是单个实现类
2、java spi 没有IOC和 AOP机制
比如,java类里依赖一个类的实例,是无法用spi机制加载,只能自己去加载,无法自动注入
但是Dubbo可以实现,具体这方面解析参考视频 https://www.bilibili.com/video/av61936651?p=5
3、JDK的spi
要用for循环,然后if判断才能获取到指定的spi对象,dubbo用指定的key就可以获取
4、JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
5、JDK的spi不支持默认值,Dubbo默认支持javassit
换句话说SPI机制,Dubbo改进了JDK标准的SPI机制
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
ExtensionLoader
//在Dubbo源码中大面积使用这种写法,都是获得某个接口的适配类,在真正执行的时候才决定最终的作用类
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//因为每一个扩展类加载器只能加载特定的SPI扩展的实现,所以要获得某个扩展的实现的话首先要找到他对应的扩展类加载器(ExtensionLoader)
//一个扩展接口的所有实现都是被同一个扩展类加载器来加载的
@SuppressWarnings("unchecked")
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!");
}
//获得某个扩展类加载器的时候该接口必须被@SPI修饰才可以
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
// 先从静态缓存中获取对应的ExtensionLoader实例,每个接口对应一个ExtensionLoader并且把映射关系都存储在缓存之中
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 为Extension类型创建ExtensionLoader实例,并放入静态缓存
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
//采用单例模式的双重判断,这种模式感觉使用的范围都比较广泛
//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("fail to create adaptive instance: " + t.toString(), t);
}
}
}
}
else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
// 创建一个接口的适配类
private T createAdaptiveExtension() {
try {
//获取AdaptiveExtensionClass并完成注入
//基本分两步:1.获取适配器类 2.在适配器里面注入其他的扩展点
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}
//获得适配类有两种途径,第一就是某个实现类上被@Adaptive注解,第二就是没有实现类被注解,因此Dubbo会自动生成一个某个接口的适配类
private Class> getAdaptiveExtensionClass() {
//如果能找到被@Adaptive注解实现类
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//找不到的话就自行创建一个适配类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
//加载当前扩展所有实现,看是否有实现类上被标注为@Adaptive
private Map> getExtensionClasses() {
//多次判断是为了防止同一个扩展点被多次加载
Map> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//loadExtensionClasses会加载所有的配置文件,将配置文件中对应的的类加载到当前的缓存中
//load完之后该classes已经保留了所有的扩展类映射关系
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map> loadExtensionClasses() {
//所有的扩展点接口都必须被SPI注释标注
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
//一个@SPI注解的值只能有一个
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));
}
//cachedDefaultName表示该扩展点对应的默认适配类的key
//逻辑运行到这里就意味着该扩展点有定义的适配类,不需要Dubbo框架自己生成适配类
if(names.length == 1) cachedDefaultName = names[0];
}
}
Map> extensionClasses = new HashMap>();
//加载对应目录下的配置文件,三个目录分别为:META-INF/services/,META-INF/dubbo,META-INF/dubbo/internal
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
//加载相关路径下的类文件
private void loadFile(Map> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration urls;
//加载这些问价你的classloader要和加载当前类的classloader一致,这个类似与Java默认的类加载器和类的加载关系
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
//该步骤就加载所有的classpath下面的同名文件(包含你的项目本地classpath和依赖jar包)
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//一般情况下每个包内只会对与每个扩展点放置一个类信息描述文件
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while ((line = reader.readLine()) != null) {
//处理注释内容
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
//SPI扩展文件中的key
name = line.substring(0, i).trim();
//SPI扩展文件中配置的value ExtensionLoader是根据key和value同时加载的
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//加载扩展类
Class> clazz = Class.forName(line, true, classLoader);
//如果配置的扩展类实现不是目标接口的实现类则直接跑错
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//如果配置的类是被@Adaptive注解的话
if (clazz.isAnnotationPresent(Adaptive.class)) {
if(cachedAdaptiveClass == null) {
//将缓存的AdaptiveClass设置成此类
cachedAdaptiveClass = clazz;
// 一个接口只能有一个适配类
} else if (! cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()+ ", " + clazz.getClass().getName()); }
} else {
try {
//判断有没有拷贝构造函数,如果有的话说明该类是实现的包装类,进行缓存。一个接口可能有多个对应的包装类实现
clazz.getConstructor(type);
Set> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
//兼容老逻辑,这里可以暂时忽略
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
//将配置的key名字根据逗号来区分
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
//自激活的实现类只会将第一个name进行存储,原因到现在还有点不清楚
cachedActivates.put(names[0], activate);
}
for (String n : names) {
//假如说配置了name1,name2=com.alibaba.DemoInterface,这时候在cachedNames中只会存储一个name1
if (! cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
//防止同一个扩展类实现被两个key共同使用
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
//通过反射自动调用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())) { //如果该扩展点实例有Set开头的公共方法
Class> pt = method.getParameterTypes()[0];//得到set方法的参数类型
try {
//得到属性名称,比如setName方法就得到name属性名称
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.invoke(instance, object);// 如果不为空,说明set方法的参数是扩展点类型,那么进行注入,意思也就是说扩展点里面还有依赖其他扩展点
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
//通过反射自动调用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())) { //如果该扩展点实例有Set开头的公共方法
Class> pt = method.getParameterTypes()[0];//得到set方法的参数类型
try {
//得到属性名称,比如setName方法就得到name属性名称
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.invoke(instance, object);// 如果不为空,说明set方法的参数是扩展点类型,那么进行注入,意思也就是说扩展点里面还有依赖其他扩展点
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
基本上讲到之类,一个适配类已经可以被加载出来了,但是由于上面的逻辑比较深,下面给出简单文字逻辑:
1.为了获得一个扩展点的适配类,首先会看缓存中有没有已经加载过的适配类,如果有的话就直接返回,没有的话就进入第2步。
2.加载所有的配置文件,将所有的配置类都load进内存并且在ExtensionLoader内部做好缓存,如果配置的文件中有适配类就缓存起来,如果没有适配类就自行通过代码自行创建适配类并且缓存起来(代码之后给出样例)。
3.在加载配置文件的时候,会依次将包装类,自激活的类都进行缓存。
4.将获取完适配类时候,如果适配类的set方法对应的属性也是扩展点话,会依次注入对应的属性的适配类(循环进行)。
Dubbo自己生成的适配类代码是怎样的(以Protocol为例):
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
使用协议Protocol根据上述的url和服务接口来引用服务,创建出一个Invoker对象
默认实现的DubboProtocol也会经过ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包装
首先看下RegistryProtocol.refer()方法,它干了哪些事呢?
将客户端的信息注册到注册中心上
创建一个RegistryDirectory,从注册中心中订阅自己引用的服务,将订阅到的url在RegistryDirectory内部转换成Invoker。
RegistryDirectory是Directory的实现,Directory代表多个Invoker,可以把它看成List类型的Invoker,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更RegistryDirectory内部含有两者重要属性:
注册中心服务Registry
Protocol 它会利用注册中心服务Registry来获取最新的服务器端注册的url地址,然后再利用协议Protocol将这些url地址转换成一个具有远程通信功能的Invoker对象,如DubboInvoker
在Directory的基础上使用Cluster将上述多个Invoker对象聚合成一个集群版的Invoker对象
本质上的做法就是通过方法的参数获得URL信息,从URL中获得对应的value对应值,然后从ExtensionLoader的缓存中找到value对应的具体实现类,然后用该实现类进行工作。可以看到上面的核心就是getExtension方法了,下面来看一下该方法的实现:
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
//DefaultExtension就是自适应的扩展类
if ("true".equals(name)) {
return getDefaultExtension();
}
//先从缓存中去取
Holder
getExtension的步骤就是根据之前load操作生成的class缓存,找到对应的Class信息,然后根据Class生成具体的实例,生成之后通过反射注入其他的属性扩展点,将包装类进行层层包装,最终返回包装过的类。(包装逻辑可以参考Protocol的包装实例)
消费者动态的构造Invoker是通过监听器,在注册中心服务变化后通过Directory调用DubboProtocol的refer方法来构造Invoker
案例介绍
先看一个简单的客户端引用服务的例子,HelloService
,dubbo
配置如下:
使用Zookeeper
作为注册中心
引用远程的HelloService
接口服务
根据之前的介绍,在Spring启动的时候,根据``配置会创建一个ReferenceBean,该bean又实现了Spring的FactoryBean接口,所以我们如下方式使用时:
@Autowired
private HelloService helloService;
使用的不是ReferenceBean对象,而是ReferenceBean的getObject()方法返回的对象,该对象通过代理实现了HelloService接口,所以要看服务引用的整个过程就需要从ReferenceBean.getObject()方法开始入手。
将ReferenceConfig.init()中的内容拆成具体的步骤,如下:
第一步:收集配置参数
methods=hello,
timestamp=1443695417847,
dubbo=2.5.3
application=consumer-of-helloService
side=consumer
pid=7748
interface=com.demo.dubbo.service.HelloService
第二步:从注册中心获取服务地址,返回Invoker对象
如果是单个注册中心,代码如下:
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
invoker = refprotocol.refer(interfaceClass, url);
第三步:使用ProxyFactory创建出Invoker的代理对象
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
proxyFactory.getProxy(invoker);
下面就详细说明下上述提到的几个概念:Protocol、Invoker、ProxyFactory
Invoker
Invoker是一个可执行对象,有三种类型的Invoker:
本地执行的Invoker(服务端使用)
远程通信执行的Invoker(客户端使用)
多个类型2的Invoker聚合成的集群版Invoker(客户端使用)
ProxyFactory
服务引用的第三步就是:
proxyFactory.getProxy(invoker);
对于Server端,ProxyFactory主要负责将服务如HelloServiceImpl统一进行包装成一个Invoker,这些Invoker通过反射来执行具体的HelloServiceImpl对象的方法
对于client端,则是将上述创建的集群版Invoker(Cluster)创建出代理对象
代码如下:
public class JavassistProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public T getProxy(Invoker invoker, Class>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
可以看到是利用jdk自带的Proxy来动态代理目标对象Invoker,所以我们调用创建出来的代理对象如HelloService的方法时,会执行InvokerInvocationHandler中的逻辑:
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker> invoker;
public InvokerInvocationHandler(Invoker> handler){
this.invoker = handler;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
//AbstractClusterInvoker.invoke()
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
1、把服务引用的信息封装成URL并注册到zk注册中心; 2、监听注册中心的服务的上下线; 3、连接服务提供端,创建NettyClient对象; 4、将这些信息包装成DubboInvoker消费端的调用链,创建消费端Invoker实例的服务代理并返回;
1、经过负载均衡策略,调用提供者; 2、选择其中一个服务的URL与提供者netty建立连接,使用ProxyFactory 创建远程通信,或者本地通信的,Invoker发到netty服务端; 3、服务器端接收到该Invoker信息后,找到对应的本地Invoker,处理Invocation请求; 4、获取异步,或同步处理结果;
异步 不需要返回值:直接调用ExchangeClient.send()方法;
同步 需要返回值:使用ExchangeClient.request()方法,返回一个ResponseFuture,一直阻塞到服务端返回响应结果;
1、需要被暴露的服务的接口类首先会通过proxyFactory代理工厂获得代理的对象invoker,而暴露之后的服务被调用,也将都会通过这个invoker来调用相关的类。
在dubbo中默认采用javassistProxyFactory来获取由javassist代理的invoker。
2、根据所要代理的类名产生相应的wrapper来对所要代理的类进行包装。
在getWrapper中,会先根据所要包装的类是否已经被包装过。如果已经包装过,则会直接在来保存已经生成的包装类的map里去寻找,否则会直接生成新的包装类。
makeWrapper()根据传递的类获取新的包装类。
在makeWrapper()方法中,通过javassist根据所需要包装的类动态生成了包装类。由于Invoker的作用,大概在这里动态实现的最重要的方法应该是invokeMothed()方法
3、在这段动态生成了invokeMethod的方法体。将会根据方法名调用包装类中真正所需要被调用的类的方法。
4、在得到了包装类之后,在javassistProxyFactory的getInvoker()方法最后会生成新的AbstractProxyInvoker(),重写了doInvoke()方法,会直接调用刚刚生成的包装类的invokeMethod()方法
5、在得到invoker之后,会根据获得的invoker开始服务的暴露,通过protocol的export()方法。配置有filter或者listener的情况下,会在这里产生关于具体服务暴露操作的过滤与监听。值得一提的是,如果采用的是registry协议,那么并不会经过ProtocolListenerWrapper的监听,而是直接进入export()方法开始服务的暴露。
6、会通过doLocalExport()方法开始本地服务的暴露。
7、本地暴露的过程中,首先会根据传入的invoker获取其提供方的url来获得key,根据获得的key来尝试获得ExporterChangeableWrapper,如果没有,则会先通过原本采用的protocol协议进行服务暴露,默认dubbo。
在dubboProtocol的export()方法中,会根据url生成server key,根据invoker和key生成新的dubboExporter并根据key和exporter存放在map里。但是最关键的是,将在这里调用openServer()方法开启与注册中心的连接。
在openServer中,如果已经建立过与服务器的连接,那么会直接在这里返回,否则会通过netty新建与注册中心的网络连接
8、在这里会通过生成ExchangeServer开始监听相应的channel并绑定对应的requestHandler进行相关的回调处理。
可见,在dubboProtocol的服务暴露过程中,完成了对网络监听的配置与开启。
在完成dubboProtocol的export()方法之后,回到RegistryProtocol的doLocalExport()方法,根据dubboProtocol暴露生成 的dubboExporter()以及invoker生成新的ExportChangeableWrapper返回。
之后,通过getRegistry获得注册中心的实例。会根据配置的registryFactory生成对应的registry实例
9、以zookeeper为例,在其构造方法当中会根据url新生成一个zkClient得到与zookeeper注册中心的连接。
在得到了注册中心的实例之后,通过register()方法正式注册服务到注册中心当中去。
在调用注册方法的过程中,被注册的url将会在zookeeperRegistry的父类的父类AbstractRegistry中保存被注册的url。
创建zk【持久型节点】、并监听如下节点,对服务提供者的configurators节点配置监听器OverrideListener /dubbo ----/com.alibaba.dubbo.demo.DemoService (providers) --------/configurators
如果URL有变化,则会触发监听器重新暴露服务
然后在zookeeperRegistry的父类当中调用zookeeperRegistry的doRegistry()方法。也就说在zookeeperRegistry的doRegistry方法当中,会将要暴露的服务信息提交给注册中心
假如服务没有配置了scope属性,或者配置了但是值不是”remote“,就会执行本地暴露。自同一个jvm内部,调用本jvm中存在的服务,就可以直接调用,而不需要走网络,减少响应时间
暴露的时候会区别:remote的话就是远程,不是就是本地暴露
url的协议已经从dubbo变成了injvm 第二步:将ref【interfaceClass的实现类】包装成一个Wrapper,并返回一个Invoker JavassistProxyFactory#getInvoker
@Adaptive
注解打在类上和方法上,他们是有区别的,Adaptive设计目的是为了识别固定已知类和扩展未知类
1、注解在类上:代表人工实现,实现一个装饰类(装饰模式),主要用于固定已知类
目前系统只有两个,AdaptiveCompiler(因为整个框架只支持Javassit和JdkComiler)、AdaptiveExtensionFactory(因为框架只支持spi和spring)
2、注解在方法上:代表自动生成和编译一个动态的Adaptive类,主要用于SPI,因为SPI的类不固定、未知的扩展类
由上图知道,本地暴露的url是以injvm
开头的,下面来看下远程暴露,其实这个也是回答本地暴露
和远程暴露
区别的一个回答点
为什么会有本地暴露
和远程暴露
呢?不从场景考虑讨论技术的没有意义是.在dubbo中我们一个服务可能既是Provider
,又是Consumer
,因此就存在他自己调用自己服务的情况,如果再通过网络去访问,那自然是舍近求远,因此他是有本地暴露
服务的这个设计.从这里我们就知道这个两者的区别
本地暴露是暴露在JVM中,不需要网络通信。
远程暴露是将IP,端口等信息暴露给远程客户端,调用时需要网络通信。
操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit 。
ASM :直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。
Javassit : 提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。
服务失效踢出基于 zookeeper 的临时节点原理
dubbo在调用服务不成功时,默认是会重试两次的。这样在服务端的处理时间超过了设定的超时时间时,就会有 重复请求,比如在发邮件时,可能就会发出多份重复邮件,执行注册请求时,就会插入多条重复的注册数据,那么怎么解决超时问题呢?如下
对于核心的服务中心,去除dubbo超时重试机制,并重新评估设置超时时间。业务处理代码必须放在服务端,客户端只做参数验证和服务调用,不涉及业务流程处理 全局配置实例
当然Dubbo的重试机制其实是非常好的QOS保证,它的路由机制,是会帮你把超时的请求路由到其他机器上,而不是本机尝试,所以 dubbo的重试机器也能一定程度的保证服务的质量。但是请一定要综合线上的访问情况,给出综合的评估
开篇说下AOP,正常来说为了不暴露最底层的接口服务,我们可以使用静态代理进行代理,问题是,总不能新增一个接口我就要去新增代理Proxy的方法,所以用动态代理(动态代理是程序运行时由JVM通过反射等机制动态生成,所以不存在代理类的字节码文件)
再来说一说Dubbo-SPI-AOP
instance = injectExtension((T) WrapperClass.getConstructor(type).newInstance(instance));
//ExtensionLoader在加载扩展点时,如果加载到扩展点由拷贝构造函数,则判定为扩展点Wrapper类
//通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点
dubbo里面的spi增加的aop,其实就是装饰者模式,但是还有弊端,这时候引入了Adaptive(扩展点自适应)。
//因为每一个扩展类加载器只能加载特定的SPI扩展的实现,所以要获得某个扩展的实现的话首先要找到他对应的扩展类加载器(ExtensionLoader)
//一个扩展接口的所有实现都是被同一个扩展类加载器来加载的
@SuppressWarnings("unchecked")
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!");
}
//获得某个扩展类加载器的时候该接口必须被@SPI修饰才可以,否则异常
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
// 先从静态缓存中获取对应的ExtensionLoader实例,每个接口对应一个ExtensionLoader并且把映射关系都存储在缓存之中
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 为Extension类型创建ExtensionLoader实例,并放入静态缓存
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}