2. wrapper 与 setter 特性
(1)如何使用
wrapper: 我们自定义的某个实现类内,如果有构造方法的入参是传入一个同类型的对象,那么即可认定这个实现类其实是一个装饰器,其实际功能由内置的那个对象来解决,该实现类本身仅相当于一个外壳,执行一些其他功能,如打印日志等。这实际上与Spring里面的功能增强类似
public class DubboProtocolWrapper implements Protocol { private Protocol protocol; // 构造方法入参是一个Protocol类型,将会被视作一个装饰器 public DubboProtocolWrapper(Protocol protocol) { this.protocol = protocol; } @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { log.debug("现在开始要导出了"); return protocol.export(invoker); } // Protocol 接口的其他方法 ...... }
Setter: 我们自定义的某个实现类内,如果有方法是以“set”作为前缀,比如是 setProtocol,并且方法有一个入参且没有 @DisableInject注解,那么就会判断该类入参是否也是一个SPI,如果是就会委托一个 ExtensionFactory 为你去找其实现类并注入,至于寻找哪个实现类,则与你ExtensionFactory的类型有关(没错,ExtensionFactory是个接口,其本身也遵循SPI接口的设计,Dubbo也为你准备了三个实现类)。这一步骤其实与Spring 的 IOC 十分相似
public class MyCluster implements Cluster{ private Protocol protocol; // 当本类被dubbo实例化时,会执行该方法,主动为我们寻找protocol的实现类并注入 public void setProtocol(Protocol protocol) { this.protocol = protocol; } // Cluster 接口的其他方法 ...... }
(2)实现原理
在实例化扩展点的代码中,我们可以看到有以下两个处理:
wrapper
如果扩展点实现类有拷贝构造函数,则认为是包装类。包装类的作用是对其他扩展点实现类进行包装,通过包装类可以把所有扩展点的公共逻辑移到包装类,类似AOP。
setter
扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader 在会自动注入依赖的扩展点。ExtensionLoader 通过扫描扩展点实现类的所有set方法来判定其成员。
//实例化扩展点时,会判断有没有wrapper装饰器,有装饰器,需要拿装饰器套壳上去,并返回装饰器实例 private T createExtension(String name) { ...... injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; ...... } //实例化扩展点时,如果实例里有其他扩展点,则也许注入扩展点 private T injectExtension(T instance) { if (objectFactory == null) { return instance; } try { for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { continue; } /** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { continue; } Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { String property = getSetterProperty(method); 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; } /** * return true if and only if: * <p> * 1, public * <p> * 2, name starts with "set" * <p> * 3, only has one parameter */ private boolean isSetter(Method method) { return method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers()); }
3. 注解 @SPI @Adaptive 和 @Activate
(1)@SPI 注解
使用方式
@SPI注解使用在接口上,作用时为该接口指定默认实现类,其属性 value 是默认实现的简称,因为是直接标在Dubbo的接口上的,除非我们修改Dubbo的源码,否则这个值就是Dubbo 内置好的(当然你也可以自己定义一个接口,给接口加上该注解)。比如下图,默认的RPC协议就是“dubbo”协议。
实现原理
// 加载某个扩展点的所有配置,所谓配置,其实就是<简称,类名>这种配置 private Map<String, Class<?>> loadExtensionClasses() { // 找到并设置本扩展点的默认实现 cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); for (LoadingStrategy strategy : strategies) { loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); } return extensionClasses; } private void cacheDefaultExtensionName() { // 如果指定扩展点接口带@SPI注解,且注解中有简称,则把该简称作为默认实现 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation == null) { return; } 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]; } } }
(2)@Adaptive 注解
使用方式
①@Adaptive注解一般在实现类上,表明该类自适应,自适应是什么意思呢?就是其某些方法自己并不实现,而是根据调用该方法时的入参,再去选另一个实现类来执行,相当于一个套壳,但和上面的装饰器类 wrapper 对比,上面的wrapper 会指定某固定类来执行方法,其本身执行别的功能,而本套壳Adaptive类,则并不固定使用哪个实现类来真正执行方法,所以叫自适应
②@Adaptive如果没有出现在实现类上,那么Duubo会在我们指定要自适应实现类的时候,给我们自己创建个自适应实现类,该实现类的模板为SPI接口本身,此时所有的方法都没有实现,仅有源码方法上有@Adaptive的部分方法才会帮你编译点方法内容,帮你解析下入参,从入参里解析出另一个实现类来执行
实现原理
当@Adaptive注解在实现类上时,会在实现类被解析时,就存储起来
// 扫描所有目录下的配置时,会对提到的各实现类进行解析(注意,仅仅是分析类信息,并没有实例化) private volatile Class<?> cachedAdaptiveClass = null; private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) throws NoSuchMethodException { ...... // 如果类上有 @Adaptive 注解,则表明该类为自适应类, 会存储进变量 cachedAdaptiveClass 中 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz, overridden); } ...... }
如果没有指定现成的带@Adaptive的实现类,那就懒加载,直到有人要获取该扩展点的自适应实现类时再进行创建
private volatile Class<?> cachedAdaptiveClass = null; // 获取本扩展点的自适应实现类 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); // 判断有没有指定的自适应实现类,如果没有则新建。 // 在前面扫描的时候,如果扫到了某个实现类上标注了@Adaptive,此时就会有自适应类 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 如果没有现成的自适应类,dubbo则会为我们创建 return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class<?> createAdaptiveExtensionClass() { // 根据SPI接口的信息,自编一个所谓的自适应实现实例。注意此处的cachedDefaultName,这代表着如果自适应没适应成功,还可以使用兜底的实现类来执行 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); } // 创建自适应实现类时,帮其完善方法 private String generateMethodContent(Method method) { Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512); // 如果接口方法上没有 @Adaptive注解,直接在方法体内写一个Unsupported异常 if (adaptiveAnnotation == null) { return generateUnsupported(method); } else { // 如果接口方法上有 @Adaptive注解 int urlTypeIndex = getUrlTypeIndex(method); // 如果方法入参不含有URL类型,方法体内写抛异常 if (urlTypeIndex != -1) { // Null Point check code.append(generateUrlNullCheck(urlTypeIndex)); } else { // did not find parameter in URL type code.append(generateUrlAssignmentIndirectly(method)); } // 此处实现较为繁琐,我们直接看其最后生成的类,进行反编译 String[] value = getMethodAdaptiveValue(adaptiveAnnotation); boolean hasInvocation = hasInvocationArgument(method); code.append(generateInvocationArgumentNullCheck(method)); code.append(generateExtNameAssignment(value, hasInvocation)); // check extName == null? code.append(generateExtNameNullCheck(value)); code.append(generateExtensionAssignment()); // return statement code.append(generateReturnAndInvocation(method)); } return code.toString(); }
如我们上述提到的 Protocol ,其接口是这样的,有两个方法带了@Adaptive
将其编造的自适应实现类,反编译,得到
(2)@Activate 注解
使用方式
@Activate注解用于标记一个扩展点实现类在什么条件下可用。通过该注解,我们可以指定这个扩展点的执行顺序、条件等,从而实现对扩展点的控制
@Activate注解可以用在四个地方:在扩展点接口上。在扩展点实现类上。在扩展点实现类的构造方法上。在扩展点实现类的构造方法参数上。
@Activate注解需要指定三个属性:group:表示扩展点所属的分组。如果不指定,则默认为全局分组,即所有扩展点都属于该分组。value:表示是否需要激活或禁用扩展点简称,允许填多个。order:表示扩展点的执行顺序,值小的先执行。如果不指定,则默认为0。
如上图,Filter的一个实现类CacheFilter,类上就标注了@Aactivate 注解,当我们用下面的方法去取实现类时,就会返回CacheFilter
public class Consumer { public static void main(String[] args) { // 获取扩展点工厂 ExtensionLoader<TestExtension> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class); // 获取Filter的扩展点实现类: // 1. 此处我们没有指明group,就不会因为group的原因筛除任何实现类, // 2. 但我们又指定了“cache”, 因此简称为 cache 的Filter的实现类一定会加入返回结果列表里 // 3. 我们指定了url,意味着对于其他的实现类,都要经过url校验,校验匹配才会加入返回结果列表里 List<TestExtension> extensions = extensionLoader.getActivateExtension(URL.valueOf("test://localhost/test?key1=value1&key2=value2"), "cache"); // 调用扩展点方法 for (TestExtension extension : extensions) { extension.test(); } } }
原理解析
// 三个重载方法,入参各不相同 public List<T> getActivateExtension(URL url, String key) { return getActivateExtension(url, key, null); } public List<T> getActivateExtension(URL url, String[] values) { return getActivateExtension(url, values, null); } public List<T> getActivateExtension(URL url, String key, String group) { String value = url.getParameter(key); return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group); } public List<T> getActivateExtension(URL url, String[] values, String group) { List<T> activateExtensions = new ArrayList<>(); // 把values里提到的简称都集合起来 List<String> names = values == null ? new ArrayList<>(0) : asList(values); if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) { getExtensionClasses(); // 遍历所有带有@Activate的实现类 for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) { String name = entry.getKey(); Object activate = entry.getValue(); String[] activateGroup, activateValue; if (activate instanceof Activate) { // 获取@Activate注解上的 group 和 value 信息 activateGroup = ((Activate) activate).group(); activateValue = ((Activate) activate).value(); } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) { activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group(); activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value(); } else { continue; } // 当遍历到一些没有明确想要,也没有明确排除的实现类,要进行判断: // 它必须满足组的要求,必须满足url的校验,才能算作校验通过 if (isMatchGroup(group, activateGroup) && !names.contains(name) && !names.contains(REMOVE_VALUE_PREFIX + name) && isActive(activateValue, url)) { activateExtensions.add(getExtension(name)); } } // 按照order排序 activateExtensions.sort(ActivateComparator.COMPARATOR); } List<T> loadedExtensions = new ArrayList<>(); // 对于一些明确想要的实现类,自然是直接放进出参的结果列表里 for (int i = 0; i < names.size(); i++) { String name = names.get(i); if (!name.startsWith(REMOVE_VALUE_PREFIX) && !names.contains(REMOVE_VALUE_PREFIX + name)) { if (DEFAULT_KEY.equals(name)) { if (!loadedExtensions.isEmpty()) { activateExtensions.addAll(0, loadedExtensions); loadedExtensions.clear(); } } else { loadedExtensions.add(getExtension(name)); } } } if (!loadedExtensions.isEmpty()) { activateExtensions.addAll(loadedExtensions); } return activateExtensions; } // 通过url 和 @Activate 里的value属性值来判断是否匹配 // 假设,现在入参的url 为 "test://localhost/test?myname=zhanfu&age=18" // 而我们有一个扩展点实现类上注解了 // @Activate( // value = {"myname:zhanfu"}, // group = {"test"}, // order = 1 // ) // 此时两者都指定了myname这个属性,且属性值都为zhanfu,这就算匹配上了 private boolean isActive(String[] keys, URL url) { if (keys.length == 0) { return true; } for (String key : keys) { // @Active(value="key1:value1, key2:value2") String keyValue = null; if (key.contains(":")) { String[] arr = key.split(":"); key = arr[0]; keyValue = arr[1]; } for (Map.Entry<String, String> entry : url.getParameters().entrySet()) { String k = entry.getKey(); String v = entry.getValue(); if ((k.equals(key) || k.endsWith("." + key)) && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) { return true; } } } return false; }
四、总结
现在我们已经知道了,dubbo-spi 实现了比 java-spi 更详尽的功能,随之而来的,肯定也是更高的学习成本。总结一下:
如果你实现了某个扩展点,配置文件需放在这三个目录下
META-INF/services/
META-INF/dubbo/internal/
META-INF/dubbo/
然后使用 ExtensionLoader.getExtensionLoader 得到扩展点加载器后,有三种获得实现类的方式
extensionLoader.getExtension(name) : 获取指定实现类
extensionLoader.getAdaptiveExtension : 获取自适应实现类
extensionLoader.getActivateExtension(url, values, group):按筛选条件获取一批实现类
另外 dubbo-spi 还有两个重要特性,与Spring容器相似,就是dubbo-spi支持
功能增强,允许构建装饰器类(与代理类似)来执行额外功能,真正的功能调用其他类实现
控制反转,会在构建一个实现类时,自动帮你注入其他实现类
上文我们已经对Dubbo的SPI机制进行了详尽的介绍,它的概念和基础使用还是十分简单的。但关于几个注解理解起来有一定复杂度,不过万事开头难,倒也不必惧怕,作为Dubbo的基础能力,如果本章内容不能牢牢掌握,则难以灵活使用和配置Dubbo,因此强烈建议进行收藏,而后时常复习,相信很快就融会贯通了