这是我参与更文挑战的第1天,活动详情查看: 更文挑战
Dubbo SPI是什么
Java SPI与Dubbo SPI的核心就是一个:**策略模式。**针对不同情况使用不同的实现。
Dubbo SPI功能
Dubbo SPI除了实现Java SPI,还增加了一些新特性。
- 扩展点自动包装(AOP)
- 扩展点自动装配(IOC)
- 扩展点自适应
- 扩展点自动激活
源码阅读
Dubbo SPI
如何使用
- 定义接口,编写实现类
// 定义接口,
@SPI // @SPI 注解标识这个接口是一个可以进行SPI扩展的接口
public interface HelloService {
String sayHello();
}
// 定义两个实现类,一个中文实现,一个英文实现
public class ChinaeseHelloService implements HelloService {
@Override
public String sayHello() {
return "你好";
}
}
public class EngishHelloService implements HelloService {
@Override
public String sayHello() {
return "Hello";
}
}
复制代码
- 定义实现类对应的key,让dubbo SPI可以查找到
dubbo SPI 通过文件方式指定接口不同实现,并且为每个实现指定一个key标识。方便在使用时指定具体实现。
文件的名称和位置需要符合dubbo定义的SPI规范
位置: resources/META-INF/dubbo 目录下
文件名称:接口的全类名,如 org.apache.dubbo.study.spi.HelloService
文件内容:实现类的key=实现类全类名,如
china=org.apache.dubbo.study.spi.ChinaeseHelloService
english=org.apache.dubbo.study.spi.EngishHelloService
复制代码
- 使用不同实现
public void spiTest() {
// 获取china实现
HelloService chinaHelloService = ExtensionLoader
.getExtensionLoader(HelloService.class)
.getExtension("china");
System.out.println(chinaHelloService.sayHello());
// 获取english实现
HelloService englishHelloService = ExtensionLoader
.getExtensionLoader(HelloService.class)
.getExtension("english");
System.out.println(englishHelloService.sayHello());
}
复制代码
源码探索
根据以上功能使用,可以看出源码入口是在ExtensionLoader.getExtensionLoader(),看源码一定要带着目的去看,比如Dubbo SPI源码主要看,如何加载配置文件、如何获取实现类对象的源码.
- 获取ExtensionLoader
HelloService chinaHelloService = ExtensionLoader
// 获取ExtensionLoader入口
.getExtensionLoader(HelloService.class)
// 根据name获取具体实现类
.getExtension("china");
System.out.println(chinaHelloService.sayHello());
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 从缓存中获取 ExtensionLoader 对象,注意缓存key是接口的Class
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {// 缓存没有进行创建
// 看下面 new ExtensionLoader 时的逻辑
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
// 创建 ExtensionLaoder
private ExtensionLoader(Class<?> type) {
this.type = type;
// ExtensionFactory 也是通过SPI加载的,自适应方式,这个地方可以先跳过,只要知道
// ExtensionFactory 是用来创建ExtensionLoader对象的即可
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
复制代码
2.根据name获取具体实现
HelloService chinaHelloService = ExtensionLoader
// 获取ExtensionLoader入口
.getExtensionLoader(HelloService.class)
// 根据name获取具体实现类入口
.getExtension("china");
System.out.println(chinaHelloService.sayHello());
// 进入getExtension("china")方法,这里面有段缓存对象的代码,单独拿出来探讨
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// 先从缓存中获取 Holder,没有则创建Holder,这个地方为什么要多封装一次Holder?
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// 如果实现类没有实例化,则进行实例化,并放入holder中
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
// getOrCreateHolder 的代码
private Holder<Object> getOrCreateHolder(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}
复制代码
下面划一下getExtension流程图
在获取Holder时候,先通过name从缓存中获取,如果缓存中不存在对应Holder,则进行创建,然后设置到缓存中。
为编写文章方便,给上面这套操作定义个别名,”getOrCreateAndSet“。
下面回到问题,为什么需要再封装一层Holder呢?
这其实是对锁粒度的一个优化,按正常来说,如果让我写获取对象的getOrCreateAndSet操作,我肯定是搞个Map<String,Object>,然后用double check,然后synchronized(Map<String,Object>)。这个时候锁的是整个缓存的Map对象,会导致在获取其它name时候也会阻塞,这实际上是不需要的,因为我们只需要在获取同一个name时候进行加锁即可。而name获取到对应Holder后,加锁的目标就可以换成Holder对象,减小了锁的粒度。
回到开头提到的问题,继续看对象如何创建的,入口方法是 createExtension。
private T createExtension(String name, boolean wrap) {
// 获取name对应实现类的Class对象,从配置文件加载的
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {// 反射创建实现类
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//... 省略了部分代码
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
复制代码
从代码里可以明显看出是通过反射实例化对象。进入getExtensionClasses找到如何加载配置文件的代码。
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();// 加载配置
cachedClasses.set(classes);
}
}
}
return classes;
}
复制代码
进入 loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
// 缓存默认实现名称,@SPI("默认名称")
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// LoadingStrategy 通过Java SPI加载,一共三个实现
// 1.加载 META-INF/dubbo/internal/ 的SPI扩展
// 2.加载 META-INF/dubbo/ 的SPI扩展
// 3.加载 META-INF/services/ 的SPI扩展
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;
}
复制代码
查看loadDirectory过程
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
//获取接口配置文件名称,如"META-INF/dubbo/internal/com.study.service.HelloService"
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
// 查找resources下的文件都是使用 ClassLoader 获取
// 不同类加载器可以加载不同范围的资源文件,比如AppClassLoader只能加载
// 项目以及第三方jar中的资源文件,无法加载rt.jar中的资源文件,因为rt.jar
// 包是有BootStrapClassLaoder加载
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 同个接口获取到多个配置文件时,会进行合并处理
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
复制代码
上面已经获取到了接口的配置文件,下一步就需要读取配置文件进行解析了,进入 loadResource 方法
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
// 读取IO流
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
String clazz = 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) {
name = line.substring(0, i).trim(); // 获取key
clazz = line.substring(i + 1).trim(); // 获取到实现类全雷鸣
} else {
clazz = line;
}
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
}
}
复制代码
可以看到源码中是直接读取IO流,然后解析出key和class,再进行加载,进入loadClass方法
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);// @Extension("实现类名称")
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);// 将加载的实现类加载进缓存中
}
}
}
}
复制代码
以上就是读取配置,反射实例化正常的Dubbo SPI扩展源码。
扩展点自动装配
即IOC,我们从源码找到一下几个问题的答案:
- 如何找到要注入的对象实例
- 注入方式是什么(对比Spring中的setter,构造器,注解注入)
- 如何处理循环依赖问题
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {// 查找setter方法
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];// 获取set的值的Class类型
if (ReflectUtils.isPrimitives(pt)) {// 参数类型是否是基本数据类型(八大数据类型+对应的包装类型,这些参数不需要进行IOC)
continue;
}
try {
String property = getSetterProperty(method);// 获取setter方法对应的属性名
Object object = objectFactory.getExtension(pt, property);// 从 SPI缓存中获取注入的对象
if (object != null) {
method.invoke(instance, object);// 调用setter方法进行注入
}
} 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;
}
复制代码
通过以上代码可以看出,注入的对象实例也是通过ExtensionLoader生成的,循环依赖问题还需要进入 objectFactory.getExtension 方法查看,就不一一点击方法了,直接列出关键代码
private T createAdaptiveExtension() {
try {
// IOC注入时候,setter值也是反射创建并且也是调用 injectExtension 注入属性
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
复制代码
说明Dubbo SPI实现的IOC没有处理循环依赖的情况,说明目前Dubbo的IOC使用常见也比较简单,没有循环依赖的情况,但可以预想,如果出现了循环依赖,就会死循环。忘记说了,IOC注入setter方法的值的Class中必须至少有一个方法打上**@Adaptive**注解,这部分代码会在扩展自适应说明。
进入getAdaptiveExtensionClass()
扩展点自动包装
自动包装即AOP,每个包装类的命名都是XXXXXXWrapper。
从源码查找一下问题:
- 如何标示需要包装的原对象
- 有JDK动态代理、ams生成字节码代理、静态代理,Dubbo SPI如何做的呢?
- 代理的顺序如何设置
private T createExtension(String name, boolean wrap) {
// 获取name对应实现类的Class对象,从配置文件加载的
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {// 反射创建实现类
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// ioc 注入
injectExtension(instance);
// aop 代理
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);// 在从配置文件加载类时,如果是代理类会加载进cachedWrapperClasses列表
wrapperClassesList.sort(WrapperComparator.COMPARATOR);// 代理升序排序
Collections.reverse(wrapperClassesList);// 逆序
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// 在真正执行时,order小的会限先执行
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
// 处理初始化
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
复制代码
加载对应的代理
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);// AOP 加载Clazz对应的Wrapper代理
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);// @Extension("实现类名称")
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);// 将加载的实现类加载进缓存中
}
}
}
}
复制代码
扩展点自适应
通常在实际使用Dubbo SPI扩展,不会直接硬编码getExtension(“china”),而是会动态的获取,如
String extensionName = "";// 可以通过rpc传递或者http传递或者注册中心传递
HelloService service = ExtensionLoader
.getExtensionLoader(HelloService.class)
.getExtension(extensionName);
System.out.println(englishHelloService.sayHello());
复制代码
而在dubbo中,在每次进行RPC调用,都会将扩展的name放在统一URL中包括开启注册中心。
URL
URL大家都不默认,全称为统一资源定位符,用于在互联网中唯一定位一个资源,其格式为:
// 以下 “[]” 中的为可选项,可填可不填,加粗为前后字符串连接符,只有前或者只有后无需该符号
[protocol]://[username]:[password]@ip:port/[path]?[a=1&b=2]
上面格式翻译过来就是 协议://用户名:密码@ip:port/资源路径?附加参数,比如以下例子都可以表示为一个url
- 192.168.1.2:3306 只有ip和端口
- www.baidu.com 通过http协议传输
在Dubbo中很巧妙的使用URL来作为传输元数据的数据结构,在使用的时候,只需要简单的拼接字符串或者分割字符串即可,不需要使用复杂低效的序列化方式传输元数据。比如:
- 在使用注册中心的场景中,使用zookeeper作为注册中心 zookeeper://192.168.2.0:22
- 在使用注册中心的场景中,使用redis作为注册中心 redis://192.168.5.4
- 在暴露服务的场景中,使用dubbo协议暴露服务 dubbo://192.168.5.4:25556/com.service.HelloService
- 在暴露服务的场景中,使用grpc协议暴露服务 grpc://192.168.22.4:232/com.service.HelloService
- 在引用服务的场景中,使用dubbo协议引用服务 dubbo://192.168.5.4:25556/com.service.HelloService
可以看到在上面的例子中,特意强调的在XXXX场景中,是因为某些URL可能会重复,比如暴露服务和引用服务都是用的dubbo协议,则URL是相同的。所以在分析URL各个参数的含义时,需要考虑当前场景。
Dubbo中URL的类模型定义如下:
class URL implements Serializable {
protected String protocol;
protected String username;
protected String password;
// by default, host to registry
protected String host;
// by default, port to registry
protected int port;
protected String path;
private final Map<String, String> parameters;
}
复制代码
而扩展点自适应,就是先定义好一个接口,比如开头定义的HelloService,有一个sayHello方法,并且入参是URL,在方法上指定@Adaptive(“hello”)注解,用来标识这个方法是一个自适应方法,类上加上@SPI注解标识这个接口是一个扩展接口。并且在开头我们也定义了两个实现,一个中文实现一个英文实现,并且配置在了SPI的文件中。
@SPI
public interface HelloService{
@Adaptive("hello")
String sayHello(URL url);
}
复制代码
在用SPI获取实现类并且调用带有Adaptive注解的方法时,会根据URL中”hello”对应的参数加载对应的实现类执行。
HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class)
.getAdaptiveExtension();
URLBuilder urlBuilder = new URLBuilder();
urlBuilder.addParameter("hello","china");// 指定china实现类
URL url = urlBuilder.build();
System.out.println(adaptiveExtension.sayHello(url));
复制代码
原理也很简单,使用getAdaptiveExtension获取自适应扩展时候,对于有@Adaptive注解的方法会进行代理,根据注解上指定的key从URL参数中获取对应的实现类的name,然后在使用SPI加载实现类执行对应方法。而Dubbo中代理方法是通过生成类实现的。下面看核心生成方法体代码。
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {// 有 Adaptive注解
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
if (urlTypeIndex != -1) {
// 从方法的参数中查找是否有URL入参
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// did not find parameter in URL type
code.append(generateUrlAssignmentIndirectly(method));// 从方法参数中找是否有获取Url的方法
}
// 通过注解指定的key,注解key可以指定多个,所有value是数组类型
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 方法的参数是否是 Invocation 类型
boolean hasInvocation = hasInvocationArgument(method);
// 生成获取 Invocation 方法名称的代码
code.append(generateInvocationArgumentNullCheck(method));
//生成通过注解指定key,从URL中获取对应值的方法,值会存在 extName 变量中
code.append(generateExtNameAssignment(value, hasInvocation));
// 生成判断 extName 是否为null的代码
code.append(generateExtNameNullCheck(value));
// 生成通过 SPI和extName 获取扩展对象的代码
code.append(generateExtensionAssignment());
// 生成执行扩展对象的代码
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
复制代码
这个生成代码的逻辑看起来相当的头疼,但这种方式的优点是不使用反射性能更高。可以通过断点查看生成的代码:
import org.apache.dubbo.common.extension.ExtensionLoader;
public class HelloService$Adaptive implements org.apache.dubbo.study.spi.HelloService {
public java.lang.String sayHello(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("hello");
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.study.spi.HelloService) name from url (" + url.toString() + ") use keys([hello])");
org.apache.dubbo.study.spi.HelloService extension = (org.apache.dubbo.study.spi.HelloService) ExtensionLoader.getExtensionLoader(org.apache.dubbo.study.spi.HelloService.class).getExtension(extName);
return extension.sayHello(arg0);
}
}
复制代码
上面的代码可以看到代理是直接调用实现类的方法,没有经过类似**method.invoke()**反射的调用,性能更高一些。
扩展点自动激活
扩展点自动激活是用在Dubbo的集合类扩展实现上,比如Filter,如果某个Filter实现类打上了@Activate(“xxx”)注解,并且”XXX”在URL中指定,就会自动激活对应的实现类,通过ExtensionLoader.getActivateExtension方法可以获取到所有激活的实现类列表。
// 删掉了一些非核心代码,注意names如果传值就相当于一个白名单,通常都会穿null
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> activateExtensions = new ArrayList<>();
TreeMap<Class, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
// names 大多数情况都是 Empty
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)// 判断是否匹配group
&& !names.contains(name) //
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) { // 注解上的值是否在URL中包含
activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
}
}
if(!activateExtensionsMap.isEmpty()){
activateExtensions.addAll(activateExtensionsMap.values());
}
}
return activateExtensions;
}
复制代码
总结
Dubbo中所有的扩展设计都是围绕URL+SPI+IOC+AOP实现的,虽然实现的代码很复杂,但不得不说使用起来非常的顺畅,这也是在设计框架时候一定要注意的地方,不论技术实现多么复杂多么抽象,多么奇怪。在设计API的时候一定要注意简洁、易用、好用。