Dubbo实践(七)扩展点

与JDK的SPI机制类似,Dubbo也在META-INF路径下定义了多种扩展接口。只是JDK SPI机制是Java后台帮你实现读取文件并对接具体的实现类,而Dubbo是自己去读文件。

扩展点配置

扩展点机制有几个要点:

1. 根据关键字去读取配置文件,获得具体的实现类

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

<dubbo:protocol name="dubbo" port="20880" />

则会根据dubbo去读取具体的协议实现类DubboProtocol.java

2. 注解@SPI和@Adaptive

  • @SPI注解:可以认为是定义默认实现类;比如Protocol接口中,定义默认协议时dubbo;
  • @Adaptive注解:该注解打在接口方法上;调ExtensionLoader.getAdaptiveExtension()获取适配类,会先通过前面的过程生成java的源代码,再通过编译器编译成class加载。但是Compiler的实现策略选择也是通过ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成class文件那岂不是要死循环下去了吗?

此时分析ExtensionLoader.getAdaptiveExtension()函数,它获取适配类不再通过前面过程生成适配类java源代码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive就把这个类作为适配类缓存在ExtensionLoader中,调用时直接返回。

3. filter和listener

在生成具体的实现类对象时,不是直接读取类文件,而是在读取类文件的基础上,通过filter和listener去封装类对象。

扩展点加载流程

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

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

1/** 2 * Protocol. (API/SPI, Singleton, ThreadSafe) 3 */ 4@SPI("dubbo") 5public interface Protocol { 6 7 /** 8 * Get default port when user doesn't config the port. 9 * 10 * @return default port 11 */ 12 int getDefaultPort(); 13 14 /** 15 * 暴露远程服务:<br> 16 * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br> 17 * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br> 18 * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br> 19 * 20 * @param <T> 服务的类型 21 * @param invoker 服务的执行体 22 * @return exporter 暴露服务的引用,用于取消暴露 23 * @throws RpcException 当暴露服务出错时抛出,比如端口已占用 24 */ 25 @Adaptive 26 <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; 27 28 /** 29 * 引用远程服务:<br> 30 * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br> 31 * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br> 32 * 3. 当url中有设置check=false时,连接失败不能抛出异常,需内部自动恢复。<br> 33 * 34 * @param <T> 服务的类型 35 * @param type 服务的类型 36 * @param url 远程服务的URL地址 37 * @return invoker 服务的本地代理 38 * @throws RpcException 当连接服务提供方失败时抛出 39 */ 40 @Adaptive 41 <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; 42 43 /** 44 * 释放协议:<br> 45 * 1. 取消该协议所有已经暴露和引用的服务。<br> 46 * 2. 释放协议所占用的所有资源,比如连接和端口。<br> 47 * 3. 协议在释放后,依然能暴露和引用新的服务。<br> 48 */ 49 void destroy(); 50 51}

对应的函数为:

1@SuppressWarnings("unchecked") 2 public T getAdaptiveExtension() { 3 Object instance = cachedAdaptiveInstance.get(); 4 if (instance == null) { 5 if (createAdaptiveInstanceError == null) { 6 synchronized (cachedAdaptiveInstance) { 7 instance = cachedAdaptiveInstance.get(); 8 if (instance == null) { 9 try { 10 instance = createAdaptiveExtension(); 11 cachedAdaptiveInstance.set(instance); 12 } catch (Throwable t) { 13 createAdaptiveInstanceError = t; 14 throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); 15 } 16 } 17 } 18 } else { 19 throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); 20 } 21 } 22 23 return (T) instance; 24 }

这里看到createAdaptiveExtension函数:

1@SuppressWarnings("unchecked") 2 private T createAdaptiveExtension() { 3 try { 4 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); 5 } catch (Exception e) { 6 throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); 7 } 8 } 9 10private Class<?> getAdaptiveExtensionClass() { 11 getExtensionClasses(); 12 if (cachedAdaptiveClass != null) { 13 return cachedAdaptiveClass; 14 } 15 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 16 }

若有cachedAdaptiveClass对象,则直接返回,否则通过生成类文件,然后complier出来。

此时我们分析getExtension函数:

1/** 2 * 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException} 3 * will be thrown. 4 */ 5 @SuppressWarnings("unchecked") 6 public T getExtension(String name) { 7 if (name == null || name.length() == 0) 8 throw new IllegalArgumentException("Extension name == null"); 9 if ("true".equals(name)) { 10 return getDefaultExtension(); 11 } 12 Holder<Object> holder = cachedInstances.get(name); 13 if (holder == null) { 14 cachedInstances.putIfAbsent(name, new Holder<Object>()); 15 holder = cachedInstances.get(name); 16 } 17 Object instance = holder.get(); 18 if (instance == null) { 19 synchronized (holder) { 20 instance = holder.get(); 21 if (instance == null) { 22 instance = createExtension(name); 23 holder.set(instance); 24 } 25 } 26 } 27 return (T) instance; 28 }

此时我们分析createExtension:

1@SuppressWarnings("unchecked") 2 private T createExtension(String name) { 3 Class<?> clazz = getExtensionClasses().get(name); 4 if (clazz == null) { 5 throw findException(name); 6 } 7 try { 8 T instance = (T) EXTENSION_INSTANCES.get(clazz); 9 if (instance == null) { 10 EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); 11 instance = (T) EXTENSION_INSTANCES.get(clazz); 12 } 13 injectExtension(instance); 14 Set<Class<?>> wrapperClasses = cachedWrapperClasses; 15 if (wrapperClasses != null && !wrapperClasses.isEmpty()) { 16 for (Class<?> wrapperClass : wrapperClasses) { 17 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 18 } 19 } 20 return instance; 21 } catch (Throwable t) { 22 throw new IllegalStateException("Extension instance(name: " + name + ", class: " + 23 type + ") could not be instantiated: " + t.getMessage(), t); 24 } 25 }

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

1private T injectExtension(T instance) { 2 try { 3 if (objectFactory != null) { 4 for (Method method : instance.getClass().getMethods()) { 5 if (method.getName().startsWith("set") 6 && method.getParameterTypes().length == 1 7 && Modifier.isPublic(method.getModifiers())) { 8 Class<?> pt = method.getParameterTypes()[0]; 9 try { 10 String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; 11 Object object = objectFactory.getExtension(pt, property); 12 if (object != null) { 13 method.invoke(instance, object); 14 } 15 } catch (Exception e) { 16 logger.error("fail to inject via method " + method.getName() 17 + " of interface " + type.getName() + ": " + e.getMessage(), e); 18 } 19 } 20 } 21 } 22 } catch (Exception e) { 23 logger.error(e.getMessage(), e); 24 } 25 return instance; 26 }

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

1Set<Class<?>> wrapperClasses = cachedWrapperClasses; 2 if (wrapperClasses != null && !wrapperClasses.isEmpty()) { 3 for (Class<?> wrapperClass : wrapperClasses) { 4 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 5 } 6 }

在分析这段代码的作用之前,我们先来分析一下 Set<Class<?>> cachedWrapperClasses是如何被赋值的,此时我们转到 private void loadFile(Map<String, Class<?>> extensionClasses, String dir) 函数:

1if (clazz.isAnnotationPresent(Adaptive.class)) { 2 if (cachedAdaptiveClass == null) { 3 cachedAdaptiveClass = clazz; 4 } else if (!cachedAdaptiveClass.equals(clazz)) { 5 throw new IllegalStateException("More than 1 adaptive class found: " 6 + cachedAdaptiveClass.getClass().getName() 7 + ", " + clazz.getClass().getName()); 8 } 9 } else { 10 try { 11 clazz.getConstructor(type); 12 Set<Class<?>> wrappers = cachedWrapperClasses; 13 if (wrappers == null) { 14 cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); 15 wrappers = cachedWrapperClasses; 16 } 17 wrappers.add(clazz); 18 } catch (NoSuchMethodException e) { 19 clazz.getConstructor(); 20 if (name == null || name.length() == 0) { 21 name = findAnnotationName(clazz); 22 if (name == null || name.length() == 0) { 23 if (clazz.getSimpleName().length() > type.getSimpleName().length() 24 && clazz.getSimpleName().endsWith(type.getSimpleName())) { 25 name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); 26 } else { 27 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); 28 } 29 } 30 } 31 String[] names = NAME_SEPARATOR.split(name); 32 if (names != null && names.length > 0) { 33 Activate activate = clazz.getAnnotation(Activate.class); 34 if (activate != null) { 35 cachedActivates.put(names[0], activate); 36 } 37 for (String n : names) { 38 if (!cachedNames.containsKey(clazz)) { 39 cachedNames.put(clazz, n); 40 } 41 Class<?> c = extensionClasses.get(n); 42 if (c == null) { 43 extensionClasses.put(n, clazz); 44 } else if (c != clazz) { 45 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); 46 } 47 } 48 } 49 } 50 }

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

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

1filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper 2listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper 3mock=com.alibaba.dubbo.rpc.support.MockProtocol

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

1Set<Class<?>> wrapperClasses = cachedWrapperClasses; 2 if (wrapperClasses != null && !wrapperClasses.isEmpty()) { 3 for (Class<?> wrapperClass : wrapperClasses) { 4 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 5 } 6 }

此时可以发现这里对instance加了装饰类;对于Protocol来说加了两个装饰类 ProtocolFilterWrapper和ProtocolListenerWrapper; 也就injectExtension 实例化包装类,并注入接口的适配器, 注意这个地方返回的是最后一个包装类。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

swap空间的增减方法

(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap

可插拔组件设计机制—SPI

SPI的全称是ServiceProviderInterface,即提供服务接口;是一种服务发现机制,SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。本篇文章聚焦SPI的使用场景及使用介绍。

Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI

!(https://oscimg.oschina.net/oscnet/up1aa4ada0efc8a144d35d25b3443d951c7e3.JPEG)SPI全称为ServiceProviderInterface,是一种服务发现机制。当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类。所以在程序中并没有直接指定使用接口