dubbo源码之SPI机制源码
在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机制源码相关推荐
- Dubbo 源码分析 - SPI 机制
1.简介 SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以 ...
- Dubbo源码解析-——SPI机制
文章目录 一.什么是SPI机制 二.Java原生的SPI机制 2.1.javaSPI示例 2.1.1.编写接口和实现类 2.1.2.编写配置文件 2.1.3.通过SPI机制加载实现类 2.1.4.JA ...
- JDK/Dubbo/Spring 三种 SPI 机制,谁更好?
点击关注公众号,Java干货及时送达 来源:juejin.cn/post/6950266942875779108 SPI 全称为 Service Provider Interface,是一种服务发现机 ...
- JDK/Dubbo/Spring 三种 SPI 机制,谁更好呢?
JDK/Dubbo/Spring 三种 SPI 机制,谁更好? SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文 ...
- dubbo源码解析-SPI机制
SPI,Service Provider Interface,服务提供者接口,是一种服务发现机制. JDK 的 SPI 规范 JDK 的 SPI 规范规定: 接口名:可随意定义 实现类名:可随 ...
- 【JVM】Java类加载器设计原理(ClassLoader源码解读/ SPI机制/ 绕开双亲委派/ 常见Java虚拟机)
目录 1. 什么是类加载器 2. 类加载器加载的过程 3. Class文件读取来源 4. 类加载器的分类 5. 那些操作会初始化类加载器 6. 类加载器的双亲委派机制 6.1 双亲委派机制机制的好处 ...
- Java是如何实现自己的SPI机制的? JDK源码(一)
注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...
- java spi机制_Java是如何实现自己的SPI机制的? JDK源码(一)
注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...
- 高级开发必须理解的Java中SPI机制
本文通过探析JDK提供的,在开源项目中比较常用的Java SPI机制,希望给大家在实际开发实践.学习开源项目提供参考. 1 SPI是什么 SPI全称Service Provider Interface ...
- Java是如何实现自己的SPI机制的?
注:该源码分析对应JDK版本为1.8 1 引言 本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是Service Provider Inte ...
最新文章
- Notepad2替代系统自带的记事本
- debian apt-get 更新源文件格式说明
- c语言变量在头文件定义变量吗,在头文件C中声明变量
- Android5.0新特性-Material Design
- 【STM32】FreeRTOS简介
- mapper同时添加数据只能添加一条_springcloud项目搭建第二节:eureka+数据库
- pipelines mysql_Scrapy爬取豆瓣图书数据并写入MySQL
- 经验 | 上交机械本硕转计算机视觉岗位面经
- Android框架之AsyncHttpClient
- CSS命名规范--BEM
- EEPROM AT24C08的操作
- 取消参考文献自动编号_参考文献的自动编号及引用
- npm create vite@latest 失败
- 黄卫龙 谈“太极起势”的练法
- 闲鱼怎么用快手做引流,快手怎么找痛点引流
- bzoj 4987 Tree
- 林轩田机器学习基石笔记6 - Theory of Generalization
- 微信小程序源码案例大全
- 使用python制作epub
- 为什么对渣土车的监控和管理如此重要
热门文章
- Sourcetree 看不了文件内容 Diff was suppressed because of file size or pattern
- Optional Interview with Benny the Irish Polyglot abo---coursera课程Learn how to learn
- 多个计算机之间触发事件,相互通信的一种方法----通过文件的办法
- php soapclient 超时,PHP SoapClient超时
- centos7查看当前系统时间、_CentOS 7修改系统时间及硬件时间
- hdu acm 1540
- 【生信进阶练习1000days】day9-BSgenome和AnnotationHub
- Httpx:针对HTTP的安全研究工具
- Modbus PLC攻击分析:从Modbus Poll/Slave到M340
- mysql如何进行宿舍分配_手把手教你做一个Jsp Servlet Mysql实现的学生宿舍管理系统...