在dubbo中,对Java中的spi机制进行了扩展,具体spi机制的使用,就不说了,在dubbo中进行了如下的扩展
1.可以通过xxxname = com.xxxx.MyFilter的配置方式,来指定自定义实现类的name
2.在使用的时候,可以根据name获取指定的扩展类,而不是像Java的spi机制直接获取所有的扩展类

在源码层面,SPI机制最为重要的类就是ExtensionLoader类,在该类中,有三个比较重要的方法
getExtension(String name)
getActivateExtension(URL url, String key)
getAdaptiveExtension()

其中,最为基础的方法是getExtension()方法,因为在该方法中,会去解析所有的扩展配置文件中配置的扩展类,然后解析在文件中配置的扩展类,是普通的扩展类,还是
@Active注解修饰的类、还是@Adaptive修饰的类、还是包装类

getExtension(String name)

在getExtension(String name)方法中,会通过DCL的方式去判断是否已经解析,如果没有解析,就会调用到
com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension
去开始解析当前type所有的扩展类

/*** 根据name创建对应的实现类,以protocol为例,如果入参http,则这里创建的是httpProtocol** 1、创建class对象* 2、属性注入* 3、AOP*/
private T createExtension(String name) {/*** 下面这一行代码中的getExtensionClasses()是最为重要的代码之一*/Class<?> clazz = getExtensionClasses().get(name);if (clazz == null) {throw findException(name);}try {T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) {/*** 1.前面都是缓存的判断,这里会根据class创建一个对象*/EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);}/*** 2.IOC进行注入依赖*/injectExtension(instance);/*** 3.aop 进行wrapper类的包装* cachedWrapperClasses这个变量是在解析META-INF.services目录下文件的时候,赋值的*/Set<Class<?>> wrapperClasses = cachedWrapperClasses;if (wrapperClasses != null && !wrapperClasses.isEmpty()) {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 + ")  could not be instantiated: " + t.getMessage(), t);}
}

根据源码可以看到,在获取某个name对应的扩展类的时候,会进行以下几个操作
1.解析完所有的扩展类之后,会先进行属性注入,这里的属性注入和spring中的概念还不太一样,这里的注入只支持通过set方法进行注入
2.通过类似于aop的操作,对包装类进行处理,将包装类包裹在目标方法外层

包装类的处理这里也依赖于getExtensionClasses()这个方法,这个方法是去解析当前所配置的所有扩展类,举个例子:比如protocol接口,在这里就会去解析dubbo自己提供的protocol接口的实现类,和我们提供的protocol实现类

解析配置文件

getExtensionClasses()

解析配置文件的原理,要从getExtensionClasses()这个方法看起:

private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();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;
}

这里的cachedClasses中存储的是一个map集合,这个map集合就是解析出来扩展类对应的name和对应的实现类

loadExtensionClasses()

/*** 这个方法返回的map是type对应的扩展类* 比如:type是protocol的时候,会返回RegistryProtocol、HttpProtocol等* @return*/private Map<String, Class<?>> loadExtensionClasses() {final SPI defaultAnnotation = type.getAnnotation(SPI.class);/*** 1.解析@SPI注解上对应的默认的实现类,只允许有一个默认的实现类,如果@SPI("dubbo,http")这样就会跑出异常* 将默认的实现,赋值到全局变量:cachedDefaultName中*/if (defaultAnnotation != null) {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.从指定的目录下的文件中解析* 在解析到之后,会赋值到extensionClasses中,在2.6之后的版本中,会兼容com.alibaba*/Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);loadDirectory(extensionClasses, DUBBO_DIRECTORY);loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;}

我们一层一层往下分析:这里的loadExtensionClasses()方法中会判断当前目标接口是否添加了@SPI注解
然后会将@SPI注解中设置的name,设置为默认的name
最为核心的是三个loadDirectory()方法

我们通常说,在自定义扩展类的时候,需要在指定的路径下,分别有三个,是哪三个呢?就是这里的

private static final String SERVICES_DIRECTORY = "META-INF/services/";private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

我们接着来看loadDirectory方法

loadDirectory()

/*** 从指定的dir目录下,查找class的实现类,通过class.forName()初始化,然后放入到extensionClasses中;* @param extensionClasses* @param dir*/
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {String fileName = dir + type.getName();try {Enumeration<java.net.URL> urls;/*** 1.获取对应的classLoader* 然后获取到对应文件中的所有url*/ClassLoader classLoader = findClassLoader();if (classLoader != null) {urls = classLoader.getResources(fileName);} else {urls = ClassLoader.getSystemResources(fileName);}if (urls != null) {while (urls.hasMoreElements()) {java.net.URL resourceURL = urls.nextElement();/*** 2.加载对应的实现类,这里的resourceURL就是文件中的:http=com.alibaba.....HttpProtocol*/loadResource(extensionClasses, classLoader, resourceURL);}}} catch (Throwable t) {logger.error("Exception when load extension class(interface: " +type + ", description file: " + fileName + ").", t);}
}

这个方法主要是根据路径地址 + typeName,拼接全类名,

loadClass

中间还有一个loadResource方法,就不贴代码了,loadResource()方法主要是根据

/*** 这个方法主要是将name和clazz赋值到extensionClasses中,只是在put之前,会有一系列的逻辑判断* @param extensionClasses* @param resourceURL* @param clazz* @param name* @throws NoSuchMethodException*/
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {/*** 1.判断clazz是否是type的实现类* 如果type是Protocol,那么这里的clazz就是文件中配置的HttpProtocol实现类*/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.");}/*** 2.判断当前实现类上是否有@Adaptive注解,需要注意的是一个接口,只允许有一个adaptive实现类* 如果有多个,就抛出异常*/if (clazz.isAnnotationPresent(Adaptive.class)) {if (cachedAdaptiveClass == null) {cachedAdaptiveClass = clazz;} else if (!cachedAdaptiveClass.equals(clazz)) {throw new IllegalStateException("More than 1 adaptive class found: "+ cachedAdaptiveClass.getClass().getName()+ ", " + clazz.getClass().getName());}} else if (isWrapperClass(clazz)) {/*** 在判断是否是包装类的时候,就看对应类中是否有目标接口的构造函数* 3.wrapper类的处理,如果当前clazz是type实现类的包装类,就暂时将包装类存入到一个集合中* CarWrapper就会进入到这里来处理*/Set<Class<?>> wrappers = cachedWrapperClasses;if (wrappers == null) {cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();wrappers = cachedWrapperClasses;}wrappers.add(clazz);} else {/*** 4.进入到这里,表示既不是包装类,也没有添加@Adaptive注解* 必须要有无参构造函数,因为是通过class.newInstance()来初始化的*/clazz.getConstructor();/*** 4.2 如果在META-INF下的文件中,没有配置name,就从实现类上去判断,是否有添加@Extension注解,如果有添加,就返回* @Extension 注解对应的value*/if (name == null || name.length() == 0) {name = findAnnotationName(clazz);if (name.length() == 0) {throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);}}/*** 4.3 对name进行拆分* 并且判断*/String[] names = NAME_SEPARATOR.split(name);if (names != null && names.length > 0) {Activate activate = clazz.getAnnotation(Activate.class);if (activate != null) {cachedActivates.put(names[0], activate);}for (String n : names) {if (!cachedNames.containsKey(clazz)) {cachedNames.put(clazz, n);}Class<?> c = extensionClasses.get(n);if (c == null) {extensionClasses.put(n, clazz);} else if (c != clazz) {throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());}}}}
}

loadClass主要是将解析到的clazz进行处理,在注释中已经写的比较明白了
1.首先判断是否是 对应的实现类
2.然后判断是否有添加@Adaptive注解,如果是第一次解析到adaptive实现类,就会放入到cachedAdaptiveClass中,如果一个接口有两个adaptive实现类,就会报错,在dubbo中是不允许的
3.然后判断是否是包装类
4.最后,除了上面的几种情况,就是一个普通的实现类了,但是也会判断是否有添加@Activate注解,如果有添加,就放入到cachedActivates中,这是一个默认激活的配置

在这个方法中,需要关注几个集合
cachedAdaptiveClass:存储的是程序员实现的adaptive实现类
cachedWrapperClasses:存储的是当前接口protocol的包装类
cachedActivates:存放的是加了@Activate注解的类
cachedNames:存储的是普通的实现类

getAdaptiveExtension()

如果我们没有声明接口的adaptive实现类,那在调用这个方法的时候,dubbo会默认帮我们生成一个adaptive实现类

/*** 获取添加了@Adaptive注解的实现类*/
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;
}private T createAdaptiveExtension() {try {return injectExtension((T) getAdaptiveExtensionClass().newInstance());} catch (Exception e) {throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);}
}private Class<?> getAdaptiveExtensionClass() {getExtensionClasses();if (cachedAdaptiveClass != null) {return cachedAdaptiveClass;}return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

这里可以看到,在调用方法的时候,会先从cachedAdaptiveClass 中取,如果没有,就调用createAdaptiveExtensionClass()方法

private Class<?> createAdaptiveExtensionClass() {String code = createAdaptiveExtensionClassCode();ClassLoader classLoader = findClassLoader();com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();return compiler.compile(code, classLoader);
}

这里createAdaptiveExtensionClassCode()方法,没太看明白,但是根据debug的结果来看,是拼接了一个类的字符串,然后通过 compiler.compile(code, classLoader);去生成一个adaptive实现类

下面是在启动的时候,dubbo生成的protocol的adaptive实现类,可以看到,export 和refer对应的实现方法中都有逻辑,但是destroy和getDefaultPort默认的实现方法中是抛出异常了,
这个adaptive的机制有关系:如果让dubbo自己动态的去生成adaptive实现类的时候,只会对目标接口中加了@Adaptive注解的方法进行实现

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive 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.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {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(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}public com.alibaba.dubbo.rpc.Exporterexport (com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {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(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);}
}
@SPI("dubbo")
public interface  Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy();
}

getActivateExtension()

/*** Get activate extensions.* 获取指定type对应的active实现类* 1.遍历所有的active实现类,cachedActivates 这个集合在调用getExtension的时候,会将实现类中加了@Active注解的类放到该集合中* 2.然后会判断group是否符合要求* 3.会判断请求该方法的时候,如果指定了names,那就过滤出来指定的active实现类* 4.接着判断注解中指定的value,在url的parameters中是否存在* @param url    url* @param values extension point names* @param group  group* @return extension list which are activated* @see com.alibaba.dubbo.common.extension.Activate*/
public List<T> getActivateExtension(URL url, String[] values, String group) {List<T> exts = new ArrayList<T>();List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {getExtensionClasses();for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {String name = entry.getKey();Activate activate = entry.getValue();/*** 1.判断activate对应的group是否符合当前入参的group*/if (isMatchGroup(group, activate.group())) {/*** 2.根据name获取对应的实现类;这里的name,应该就是文件中filter对应的key* 3.在isActive()方法中,判断当前url是否包含filter指定的value*/T ext = getExtension(name);if (!names.contains(name)&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)&& isActive(activate, url)) {exts.add(ext);}}}Collections.sort(exts, ActivateComparator.COMPARATOR);}List<T> usrs = new ArrayList<T>();for (int i = 0; i < names.size(); i++) {String name = names.get(i);if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {if (Constants.DEFAULT_KEY.equals(name)) {if (!usrs.isEmpty()) {exts.addAll(0, usrs);usrs.clear();}} else {T ext = getExtension(name);usrs.add(ext);}}}if (!usrs.isEmpty()) {exts.addAll(usrs);}return exts;
}

这里面比较核心的是isActive()方法,

/*** 判断active注解中的value* 如果我们在实现类上的active注解中,配置了value参数,表示在buildFilter的时候,不仅要满足group,还要满足:*  在请求的url中,对应的parameter有value的值,且其值不为null* @param activate:name对应的实现类上,指定的value* @param url* @return*/
private boolean isActive(Activate activate, URL url) {String[] keys = activate.value();if (keys.length == 0) {return true;}for (String key : keys) {for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {String k = entry.getKey();String v = entry.getValue();if ((k.equals(key) || k.endsWith("." + key))&& ConfigUtils.isNotEmpty(v)) {return true;}}}return false;
}

总结

所以,对于dubbo的SPI机制,最为核心的是对所有扩展方法的解析,active和adaptive都依赖于对配置文件中,自定义实现类的解析,最后会对其进行wrapper包装类的处理,最为典型的应用:就是filter的使用,在服务暴露和服务引入的过程中,会经过一系列包装类和filter的处理,其依赖的就是SPI机制去解析的

dubbo源码之SPI机制源码相关推荐

  1. Dubbo 源码分析 - SPI 机制

    1.简介 SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以 ...

  2. Dubbo源码解析-——SPI机制

    文章目录 一.什么是SPI机制 二.Java原生的SPI机制 2.1.javaSPI示例 2.1.1.编写接口和实现类 2.1.2.编写配置文件 2.1.3.通过SPI机制加载实现类 2.1.4.JA ...

  3. JDK/Dubbo/Spring 三种 SPI 机制,谁更好?

    点击关注公众号,Java干货及时送达 来源:juejin.cn/post/6950266942875779108 SPI 全称为 Service Provider Interface,是一种服务发现机 ...

  4. JDK/Dubbo/Spring 三种 SPI 机制,谁更好呢?

    JDK/Dubbo/Spring 三种 SPI 机制,谁更好? SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文 ...

  5. dubbo源码解析-SPI机制

    SPI,Service Provider Interface,服务提供者接口,是一种服务发现机制. JDK 的 SPI 规范 JDK 的 SPI 规范规定:  接口名:可随意定义  实现类名:可随 ...

  6. 【JVM】Java类加载器设计原理(ClassLoader源码解读/ SPI机制/ 绕开双亲委派/ 常见Java虚拟机)

    目录 1. 什么是类加载器 2. 类加载器加载的过程 3. Class文件读取来源 4. 类加载器的分类 5. 那些操作会初始化类加载器 6. 类加载器的双亲委派机制 6.1 双亲委派机制机制的好处 ...

  7. Java是如何实现自己的SPI机制的? JDK源码(一)

    注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...

  8. java spi机制_Java是如何实现自己的SPI机制的? JDK源码(一)

    注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...

  9. 高级开发必须理解的Java中SPI机制

    本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践.学习开源项目提供参考. 1 SPI是什么 SPI全称Service Provider Interface ...

  10. Java是如何实现自己的SPI机制的?

    注:该源码分析对应JDK版本为1.8   1 引言 本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是Service Provider Inte ...

最新文章

  1. Notepad2替代系统自带的记事本
  2. debian apt-get 更新源文件格式说明
  3. c语言变量在头文件定义变量吗,在头文件C中声明变量
  4. Android5.0新特性-Material Design
  5. 【STM32】FreeRTOS简介
  6. mapper同时添加数据只能添加一条_springcloud项目搭建第二节:eureka+数据库
  7. pipelines mysql_Scrapy爬取豆瓣图书数据并写入MySQL
  8. 经验 | 上交机械本硕转计算机视觉岗位面经
  9. Android框架之AsyncHttpClient
  10. CSS命名规范--BEM
  11. EEPROM AT24C08的操作
  12. 取消参考文献自动编号_参考文献的自动编号及引用
  13. npm create vite@latest 失败
  14. 黄卫龙 谈“太极起势”的练法
  15. 闲鱼怎么用快手做引流,快手怎么找痛点引流
  16. bzoj 4987 Tree
  17. 林轩田机器学习基石笔记6 - Theory of Generalization
  18. 微信小程序源码案例大全
  19. 使用python制作epub
  20. 为什么对渣土车的监控和管理如此重要

热门文章

  1. Sourcetree 看不了文件内容 Diff was suppressed because of file size or pattern
  2. Optional Interview with Benny the Irish Polyglot abo---coursera课程Learn how to learn
  3. 多个计算机之间触发事件,相互通信的一种方法----通过文件的办法
  4. php soapclient 超时,PHP SoapClient超时
  5. centos7查看当前系统时间、_CentOS 7修改系统时间及硬件时间
  6. hdu acm 1540
  7. 【生信进阶练习1000days】day9-BSgenome和AnnotationHub
  8. Httpx:针对HTTP的安全研究工具
  9. Modbus PLC攻击分析:从Modbus Poll/Slave到M340
  10. mysql如何进行宿舍分配_手把手教你做一个Jsp Servlet Mysql实现的学生宿舍管理系统...