dubboSPI机制浅谈

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

本文重点讲述SPI机制,从jdk和dubbo

1、jdk spi机制

2、dubbo spi实现

首先spi是什么?

SPI是为某个接口寻找服务实现的机制。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

其次java spi是怎么找到实现类的?

java spi和所有实现接口的厂商有一个俗称的约定,只要将META-INF/services文件夹下生成一个和抽象类全名称(路径+类名称)相同的配置文件,那么厂商的jar包只要在工程路径下就能找到实现类。这种方式主要是解决不同厂商不同实现类的加载问题。在不修改java文件的情况下,如何才能够定位到不同的实现类。

JDK的spi机制有一个缺点,就是如果多个厂商的spi实现的jar包都在路径下,那么就要加载所有的实现类,这样很浪费资源。dubbo中每一种接口都有很多种实现,如果使用jdk这种方式自然做不到优雅的根据一个接口来获得该接口的实现。dubbo的目标就是:根据你配置的name获取某一个特定的接口实现,没有用到的其他接口实现就不能被实例化,免得浪费。因此,dubbo就按照SPI机制的原理自己实现了一套扩展机制。为实现dubbo的扩展机制,dubbo的SPI做到了一下三个方面。

1 可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能
2 对于扩展实现IOC   依赖注入功能

3 对扩展采用装饰器模式进行功能增强,类似AOP实现的功能

dubbo的SPI实现具体如下。

首先定义一个SPI注解类

@Documented
@Retention
(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

/**
    * 缺省扩展点名。
    */
String value() default "";

}

dubbo里面很多的接口都打了SPI注解,这些注解的实现要从一下三个文件夹下去寻找实现。

META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了

META-INF/dubbo

/META-INF/services/


145046_jlwr_166980.jpg


文件里面的内容全部都是key-value的形式存在。dubbo的各种接口有很多类型的实现。拿Protocol举例,它的实现:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等。dubbo的扩展机制如何去查找你的实现类,并实现调用的呢?

ExtensionLoader<T>
拿ServiceConfig<T>中的这两个变量举例子。
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
这两个变量最终生成的既不是接口也不是具体的实现类,而是一个接口适配器类。这个适配器类动态的生成的,如下所示的代码:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
//根据url的信息去获取真实的实现类
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
}

首先,我们来看适配器类是如何生成

com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);


第一步:通过proxyFactory.class类型生成一个ExtensionLoader<T>实例。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {每个定义的spi的接口都会构建一个ExtensionLoader实例,存储在EXTENSION_LOADERS
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;}


第二步:创建Adaptive实例,放入数组cachedAdaptiveInstance中。

public T getAdaptiveExtension() {Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {//创建Adaptive实例,放入数组cachedAdaptiveInstance中。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 extenstion " + type + ", cause: " + e.getMessage(), e);}
}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();//编译这个类适配器为class文件
return compiler.compile(code, classLoader);
}
// 此方法已经getExtensionClasses方法同步过。
private Map<String, Class<?>> loadExtensionClasses() {//先读取SPI注解的value值,有值作为默认扩展实现的key
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {String value = defaultAnnotation.value();
if(value != null && (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];}}Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();//读取三个文件夹下的文件,将key-class放置到extensionClasses中loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);loadFile(extensionClasses, DUBBO_DIRECTORY);loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
//获取文件路径 目录+type名称String fileName = dir + type.getName();try {Enumeration<java.net.URL> urls;//类加载器ClassLoader classLoader = findClassLoader();if (classLoader != null) {urls = classLoader.getResources(fileName);} else {urls = ClassLoader.getSystemResources(fileName);}if (urls != null) {while (urls.hasMoreElements()) {java.net.URL url = urls.nextElement();try {BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));try {String line = 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();line = line.substring(i + 1).trim();}if (line.length() > 0) {
//读取到类Class<?> clazz = Class.forName(line, true, classLoader);
//判断实现类是否实现了type接口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.");}
//类中是否有方法打了Adaptive注解if (clazz.isAnnotationPresent(Adaptive.class)) {
//将打了Adaptive注解的类放置到cachedAdaptiveClass 中去。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 {//判断该是否有实现类是否存在入参为接口的构造器clazz.getConstructor(type);Set<Class<?>> wrappers = cachedWrapperClasses;if (wrappers == null) {cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();wrappers = cachedWrapperClasses;}//按照装饰器的角色将该类加进来wrappers.add(clazz);} // end of while urls}} catch (Throwable t) {logger.error("Exception when load extension class(interface: " +type + ", description file: " + fileName + ").", t);}
}

适配器中发现这个实现类也是由这个方法生成extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName)


//创建扩展类
instance = createExtension(name);
private T createExtension(String name) {
//加载类Class<?> clazz = getExtensionClasses().get(name);if (clazz == null) {throw findException(name);}try {//从容器中获取是否存在T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) {//容器中不存在,则按照类-实例对的形式放置到容器中EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());//获取该实例数据对instance = (T) EXTENSION_INSTANCES.get(clazz);}//set注入参数injectExtension(instance);//获取包装器类。class com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper   class com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperSet<Class<?>> wrapperClasses = cachedWrapperClasses;if (wrapperClasses != null && wrapperClasses.size() > 0) {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);}
}



dubbo的SPI扩展机制,使得我们给出url就能动态的去获取真实的实现类。获得到真实的实现类,是实现功能的第一步。下面我们来看看spring如何加载到dubbo解析器类,并将dubbo功能收入囊下的。未完待续。


参考文献

http://m.blog.csdn.net/blog/u010311445/41577235





转载于:https://my.oschina.net/zjItLife/blog/530923

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/543952.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

彻底搞懂 SpringBoot 中的 starter 机制

前言我们都知道&#xff0c;Spring的功能非常强大&#xff0c;但也有些弊端。比如&#xff1a;我们需要手动去配置大量的参数&#xff0c;没有默认值&#xff0c;需要我们管理大量的jar包和它们的依赖。为了提升Spring项目的开发效率&#xff0c;简化一些配置&#xff0c;Sprin…

Java 中验证时间格式的 4 种方法

大家好&#xff0c;今天咱们来讲一下&#xff0c;Java 中如何检查一个字符串是否是合法的日期格式&#xff1f;为什么要检查时间格式&#xff1f;后端接口在接收数据的时候&#xff0c;都需要进行检查。检查全部通过后&#xff0c;才能够执行业务逻辑。对于时间格式&#xff0c…

Redis 实现分布式锁的 7 种方案

前言日常开发中&#xff0c;秒杀下单、抢红包等等业务场景&#xff0c;都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开&#xff0c;跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方&#xff0c;欢迎大家指出哈&#xff0c;一起学习一…

css复选框样式_使用CSS样式复选框

css复选框样式Introduction: 介绍&#xff1a; Sometimes we want to develop a website or web page that would contain a form and through that form, we want to get some information from the user. Now that information could be of any type depending on the kind …

javascript对话框_JavaScript中的对话框

javascript对话框JavaScript对话框 (JavaScript Dialog Boxes) Dialog boxes are a great way to provide feedback to the user when they submit a form. In JavaScript, there are three kinds of Dialog boxes, 对话框是向用户提交表单时提供反馈的好方法。 在JavaScript中…

排查死锁的 4 种工具,秀~

作者 | 磊哥来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;死锁&#xff08;Dead Lock&#xff09;指的是两个或两个以上的运算单元&#xff08;进程、线程或协程&#xff09;&#xf…

MySQL 常见的 9 种优化方法

大家好&#xff0c;我是磊哥&#xff01;今天给大家分享一些简单好用的数据库优化方式&#xff01;1、选择最合适的字段属性Mysql是一种关系型数据库&#xff0c;可以很好地支持大数据量的存储&#xff0c;但是一般来说&#xff0c;数据库中的表越小&#xff0c;在它上面执行的…

oracle中dbms_DBMS中的实例和架构

oracle中dbms1)实例 (1) Instances) What is the Instance? If we look towards it in real life, we refer instance as an occurrence of something at a particular moment of time. In Database Management system, there are a lot of changes occurring over time to th…

过滤器和拦截器的 5 个区别!

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;过滤器&#xff08;Filter&#xff09;和拦截器&#xff08;Interceptor&#xff09;都是基于 AOP&#xff08;Aspec…

面试突击第一季完结:共 91 篇!

感谢各位读者的支持与阅读&#xff0c;面试突击系列第一季到这里就要和大家说再见了。希望所写内容对大家有帮助&#xff0c;也祝你们找到满意的工作。青山不改&#xff0c;细水长流&#xff0c;我们下一季再见&#xff01;91&#xff1a;MD5 加密安全吗&#xff1f;90&#xf…

SpringBoot官方热部署和远程调试神器

平时使用SpringBoot开发应用时&#xff0c;修改代码后需要重新启动才能生效。如果你的应用足够大的话&#xff0c;启动可能需要好几分钟。有没有什么办法可以加速启动过程&#xff0c;让我们开发应用代码更高效呢&#xff1f;今天给大家推荐一款SpringBoot官方的热部署工具spri…

MySQL 优化:Explain 执行计划详解

昨天中午在食堂&#xff0c;和部门的技术大牛们坐在一桌吃饭&#xff0c;作为一个卑微技术渣仔默默的吃着饭&#xff0c;听大佬们高谈阔论&#xff0c;研究各种高端技术&#xff0c;我TM也想说话可实在插不上嘴。聊着聊着突然说到他上午面试了一个工作6年的程序员&#xff0c;表…

顶级 Javaer 常用的 14 个类库

作者&#xff1a;小姐姐味道昨天下载下来Java16尝尝鲜。一看&#xff0c;好家伙&#xff0c;足足有176MB大。即使把jmc和jvisualvm给搞了出去&#xff0c;依然还是这么大&#xff0c;真的是让人震惊不已。但即使JDK足够庞大&#xff0c;它的功能也已经不够用了。我们需要借助于…

势头迅猛的儿童手表:恐陷下一个文曲星之地?

历史的节奏&#xff0c;就是不断重复此前发生过的事。虽然表现形态不一&#xff0c;但蕴藏的规律、趋势总是有着惊人的相似。在科技行业&#xff0c;同样如此——iPhone开启的智能手机时代走过的大兴—→平稳→下降态势&#xff0c;与PC的历程几乎是一样的。而在国内&#xff0…

scala 类中的对象是类_Scala中的类和对象

scala 类中的对象是类Scala中的课程 (Classes in Scala) A class is a blueprint for objects. It contains the definition of all the members of the class. There are two types of members of the class in Scala, 类是对象的蓝图。 它包含该类的所有成员的定义。 Scala中…

2022年终总结:不再用“拼命”来应对极度的不安全感

作者 | 磊哥来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;人生匆匆三十四余载&#xff0c;今天又到了辞旧迎新和 2022 年说再&#xff08;也不&#xff09;见的时刻了&#xff0c;所以…

Java 最常见的 200+ 面试题:面试必备

这份面试清单是从我 2015 年做了 TeamLeader 之后开始收集的&#xff0c;一方面是给公司招聘用&#xff0c;另一方面是想用它来挖掘在 Java 技术栈中&#xff0c;还有那些知识点是我不知道的&#xff0c;我想找到这些技术盲点&#xff0c;然后修复它&#xff0c;以此来提高自己…

vim中的jk为什么是上下_JK轮胎的完整形式是什么?

vim中的jk为什么是上下JK轮胎&#xff1a;Juggilal Kamlapat Ji轮胎 (JK Tyres: Juggilal Kamlapat Ji Tyres) JK Tyre and Industries is an abbreviation of Juggilal Kamlapat Ji Tyres & Industries Ltd. It is an Automobile Tyre, Tubes and Flaps manufacturing com…

【C语言】第二章 类型、运算符和表达式

为什么80%的码农都做不了架构师&#xff1f;>>> 变量和常量是程序处理的两种基本数据对象。 声明语句说明变量的名字及类型&#xff0c;也可以指定变量的初值。 运算符指定要进行的操作。 表达式则把变量与常量组合起来生成新的值。 对象的类型决定该对象可取值的集…

转: 加快Android编译速度

转&#xff1a; http://timeszoro.xyz/2015/11/25/%E5%8A%A0%E5%BF%ABandroid%E7%BC%96%E8%AF%91%E9%80%9F%E5%BA%A6/ 加快Android编译速度 发表于 2015-11-25 | 对于Android开发者而言&#xff0c;随着工程不断的壮大&#xff0c;Android项目的编译时间也逐渐变长&#xff…