一、SPI
SPI全称为Service Provider Interface,对应中文为服务发现机制。SPI相似一种可插拔机制,首先须要定义一个接口或一个约定,而后不同的场景能够对其进行实现,调用方在应用的时候无需过多关注具体的实现细节。在Java中,SPI体现了面向接口编程的思维,满足开闭设计准则。
1.1 JDK自带SPI实现
从JDK1.6开始引入SPI机制后,能够看到很多应用SPI的案例,比方最常见的数据库驱动实现,在JDK中只定义了java.sql.Driver的接口,具体实现由各数据库厂商来提供。上面一个简略的例子来疾速理解下Java SPI的应用形式:
1)定义一个接口
package com.vivo.studypublic interface Car {void getPrice();}
2)接口实现
package com.vivo.study.impl /** * 实现一 * */public class AudiCar implements Car { @Override public void getPrice() { System.out.println("Audi A6L's price is 500000 RMB."); }} package com.vivo.study.impl/** * 实现二 * */public class BydCar implements Car { @Override public void getPrice() { System.out.println("BYD han's price is 220000 RMB."); }}
3)挂载扩大类信息
在META-INF/services目录下以接口全名为文件名的文本文件,对应此处即在META-INF/services目录下创立一个文件名为com.vivo.study.Car的文件,文件内容如下:
com.vivo.study.impl.AudiCarcom.vivo.study.impl.BydCar
4)应用
public class SpiDemo {public static void main(String[] args) { ServiceLoader<Car> load = ServiceLoader.load(Car.class); Iterator<Car> iterator = load.iterator();while (iterator.hasNext()) { Car car = iterator.next(); car.getPrice(); } }}
下面的例子简略的介绍了JDK SPI机制的应用形式,其中最要害的类为ServiceLoader,通过ServiceLoader类来加载接口的实现类,ServiceLoader是Iterable接口的实现类,对于ServiceLoader加载的具体过程此处不开展。
JDK对SPI的加载实现存在一个较为突出的小毛病,无奈按需加载实现类,通过ServiceLoader.load加载时会将文件中的所有实现都进行实例化,如果想要获取具体某个具体的实现类须要进行遍历判断。
1.2 Dubbo SPI
SPI扩大是Dubbo的最大的长处之一,反对协定扩大、调用拦挡扩大、援用监听扩大等等。在Dubbo中,依据不同的扩大定位,扩大文件别离被搁置在META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个门路下。
Dubbo中有间接应用JDK SPI实现的形式,比方org.apache.dubbo.common.extension.LoadingStrategy放在META-INF/services/门路下,但大多状况下都是应用其本身对JDK SPI的实现的一种优化形式,可称为Dubbo SPI,也就是本文要解说的点。
相比于JDK的SPI的实现,Dubbo SPI具备以下特点:
配置模式更灵便:反对以key:value的模式在文件里配置相似name:xxx.xxx.xxx.xx,后续能够通过name来进行扩大类按需精准获取。
缓存的应用:应用缓存晋升性能,保障一个扩大实现类至少会加载一次。
对扩大类细分扩大:反对扩大点主动包装(Wrapper)、扩大点主动拆卸、扩大点自适应(@Adaptive)、扩大点主动激活(@Activate)。
Dubbo对扩大点的加载次要由ExtensionLoader这个类开展。
二、加载-ExtensionLoader
ExtensionLoader在Dubbo里的角色相似ServiceLoader在JDK中的存在,用于加载扩大类。在Dubbo源码里,随处都能够见到ExtensionLoader的身影,比方在服务裸露里的要害类ServiceConfig中等,弄清楚ExtensionLoader的实现细节对浏览Dubbo源码有很大的帮忙。
2.1 获取ExtensionLoader的实例
ExtensionLoader没有提供共有的构造函数,
只能通过ExtensionLoader.getExtensionLoader(Class<T> type)来获取ExtensionLoader实例。
public // ConcurrentHashMap缓存,key -> Class value -> ExtensionLoader实例 private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64); private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); } 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!"); } // 查看接口是否是被@SPI注解润饰,如果没有则抛出异样 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } // 从缓存里取,没有则初始化一个并放入缓存EXTENSION_LOADERS中 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }}
下面的代码展现了获取ExtensionLoader实例的过程,能够看出,每一个被@SPI润饰的接口都会对应同一个ExtensionLoader实例,且对应ExtensionLoader只会被初始化一次,并缓存在ConcurresntHashMap中。
2.2 加载扩大类
加载扩大类入口,当应用ExtensionLoader时,getExtensionName、getActivateExtension或是getDefaultExtension都要通过getExtensionClasses办法来加载扩大类,如下图;
getExtensionClasses办法调用的门路如下图,getExtensionClasses是加载扩大类的一个终点,会首先从缓存中获取,如果缓存中没有则通过loadExtensionClasses办法来加载扩大类,所以说实际上的加载逻辑入口在loadExtensionClasses。
getExtensionClasses |->loadExtensionClasses |->cacheDefaultExtensionName |->loadDirectory |->loadResource |->loadClass
2.2.1 loadExtensionClasses加载扩大类
因为整个加载过程设计的源码较多,因而用一个流程图来进行形容,具体细节能够联合源码进行查看。
loadExtensionClasses次要做了以下这几件事:
默认扩展名:
抽取默认扩大实现名并缓存在ExtensionLoader里的cachedDefaultName,默认扩展名配置通过@SPI注解在接口上配置,如配置@SPI("defaultName")则默认扩大名为defaultName。
加载扩大类信息:
从META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个门路下寻找以类的全路径名命名的文件,并逐行读取文件里的内容。
加载class并缓存:
对扩大类分为自适应扩大实现类(被@Adaptive润饰的实现类)、包装类(领有一个只有一个为这个接口类型的参数的构造方法)、一般扩大类,其中一般扩大类中又蕴含主动激活扩大类(被@Activate润饰的类)和真一般的类,对自适应扩大实现类、包装类、主动激活扩大类这三种类型的类别离加载并别离缓存到cachedAdaptiveClass、cachedActivates、cachedWrapperClasses。
返回后果Map<String, Class<?>>:
后果返回Map,其中key对应扩大文件里配置的name,value对应扩大的类class,最初在getExtensionClasses办法里会将此后果放入缓存cachedClasses中。此后果Map中蕴含除了自适应扩大实现类和包装实现类的其余所用的扩大类名与对应类的映射关系。
通过loadExtensionClasses办法把扩大类(Class对象)都加载到相应的缓存中,是为了不便前面实例化扩大类对象,通过newInstance()等办法来实例化。
2.2.2扩大包装类
什么是扩大包装类?是不是类名结尾蕴含了Wrapper的类就是扩大包装类?
在Dubbo SPI接口的实现扩大类中,如果此类蕴含一个此接口作为参数的构造方法,则为扩大包装类。扩大包装类的作用是持有具体的扩大实现类,能够一层一层的被包装,作用相似AOP。
包装扩大类的作用是相似AOP,不便扩大加强。具体实现代码如下:
从代码中能够得出,能够通过boolean wrap抉择是否应用包装类,默认状况下为true;如果有扩大包装类,理论的实现类会被包装类按肯定的程序一层一层包起来。
如Protocol的实现类ProtocolFilterWrapper、ProtocolListenerWrapper都是扩大包装类。
2.2.3自适应扩大实现类
2.2.3.1 @Adaptive
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface Adaptive {String[] value() default {};}
从源码以及源码正文中能够得出以下几点:
Adaptive是一个注解,能够润饰类(接口,枚举)和办法。
此注解的作用是为ExtensionLoader注入扩大实例提供有用的信息。
从正文中了解value的作用:
value能够决定抉择应用具体的扩大类。
通过value配置的值key,在润饰的办法的入参org.apache.dubbo.common.URL中通过key获取到对应的值value,依据value作为extensionName去决定应用对应的扩大类。
如果通过2没有找到对应的扩大,会抉择默认的扩大类,通过@SPI配置默认扩大类。
2.2.3.2 @Adaptive简略例子
因为@Adaptive润饰类时比拟好了解,这里举一个@Adaptive润饰办法的例子,应用@Adaptive润饰办法的这种状况在Dubbo也是随处可见。
/*** Dubbo SPI 接口*/@SPI("impl1")public interface SimpleExt { @Adaptive({"key1", "key2"}) String yell(URL url, String s);}
如果调用
ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension().yell(url, s)办法,最终调用哪一个扩大类的实例去执行yell办法的流程大抵为:先获取扩大类的名称extName(对应下面说的name:class中的name),而后通过extName来获取对应的类Class,再实例化进行调用。所以要害的步骤在怎么失去extName,下面的这个例子失去extName的流程为:
通过url.getParameters.get("key1")获取,
没有获取到则用url.getParameters.get("key2"),如果还是没有获取到则应用impl1对应的实现类,最初还是没有获取到则抛异样IllegalStateException。
能够看出,@Adaptive的益处就是能够通过办法入参决定具体调用哪一个实现类。上面会对@Adaptive的具体实现进行详细分析。
2.2.3.3 @Adaptive加载流程
流程关键点阐明:
1)黄色标记的,cachedAdaptiveClass是在ExtensionLoader#loadClass办法中加载Extension类时缓存的。
2)绿色标记的,如果Extension类中存在被@Adaptive润饰的类时会应用该类来初始化实例。
3)红色标记的,如果Extension类中不存在被@Adaptive润饰的类时,则须要动静生成代码,通过javassist(默认)来编译生成Xxxx$Adaptive类来实例化。
4)实例化后通过injectExtension来将Adaptive实例的Extension注入(属性注入)。
后续围绕上述的关键点3具体开展,关键点4此处不开展。
动静生成Xxx$Adaptive类:上面的代码为动静生成Adaptive类的相干代码,具体生成代码的细节在AdaptiveClassCodeGenerator#generate中
public class ExtensionLoader<T> {// ...private Class<?> getAdaptiveExtensionClass() {// 依据对应的SPI文件加载扩大类并缓存,细节此处不开展 getExtensionClasses();// 如果存在被@Adaptive润饰的类则间接返回此类if (cachedAdaptiveClass != null) {return cachedAdaptiveClass; }// 动静生成Xxxx$Adaptive类return cachedAdaptiveClass = createAdaptiveExtensionClass(); }private Class<?> createAdaptiveExtensionClass() {// 生成Xxxx$Adaptive类代码,可自行加日志或断点查看生成的代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader();// 获取动静编译器,默认为javassist org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();return compiler.compile(code, classLoader); }}
AdaptiveClassCodeGenerator#generate生成code的形式是通过字符串拼接,大量应用String.format,整个代码过程比拟繁琐,可通过debug去理解细节。
最要害的的局部是生成被@Adaptive润饰的办法的内容,也就是最终调用实例的@Adaptive办法时,可通过参数来动静抉择具体应用哪个扩大实例。上面对此局部进行剖析:
public class AdaptiveClassCodeGenerator {// .../** * generate method content */private String generateMethodContent(Method method) {// 获取办法上的@Adaptive注解 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512);if (adaptiveAnnotation == null) {// 办法时没有@Adaptive注解,生成不反对的代码return generateUnsupported(method); } else {// 办法参数里URL是第几个参数,不存在则为-1int urlTypeIndex = getUrlTypeIndex(method);// found parameter in URL typeif (urlTypeIndex != -1) {// Null Point check code.append(generateUrlNullCheck(urlTypeIndex)); } else {// did not find parameter in URL type code.append(generateUrlAssignmentIndirectly(method)); }// 获取办法上@Adaptive配置的value// 比方 @Adaptive({"key1","key2"}),则会返回String数组{"key1","key2"}// 如果@Adaptive没有配置value,则会依据简写接口名按驼峰用.宰割,比方SimpleExt对应simple.ext String[] value = getMethodAdaptiveValue(adaptiveAnnotation);// 参数里是否存在org.apache.dubbo.rpc.Invocation boolean hasInvocation = hasInvocationArgument(method); code.append(generateInvocationArgumentNullCheck(method));// 生成String extName = xxx;的代码 ,extName用于获取具体的Extension实例 code.append(generateExtNameAssignment(value, hasInvocation));// check extName == null? code.append(generateExtNameNullCheck(value)); code.append(generateExtensionAssignment());// return statement code.append(generateReturnAndInvocation(method)); }return code.toString(); }}
上述生成Adaptive类的办法内容中最要害的步骤在生成extName的局部,也就是generateExtNameAssignment(value,hasInvocation),此办法if太多了(有点目迷五色)。
以下列举几个例子对此办法的实现流程进行简略展现:假如办法中的参数不蕴含org.apache.dubbo.rpc.Invocation,蕴含org.apache.dubbo.rpc.Invocation的状况会更加简单。
1)办法被@Adaptive润饰,没有配置value,且在接口@SPI上配置了默认的实现
@SPI("impl1")public interface SimpleExt {@AdaptiveString echo(URL url, String s);}
对应生成extName的代码为:
String extName = url.getParameter("simple.ext", "impl1")
2)办法被@Adaptive润饰,没有配置value,且在接口@SPI上没有配置默认的实现
@SPI("impl1")public interface SimpleExt {@Adaptive({"key1", "key2"})String yell(URL url, String s);}
对应生成extName的代码为:
String extName = url.getParameter( "simple.ext")
3)办法被@Adaptive润饰,配置了value(假如两个,顺次类推),且在接口@SPI上配置了默认的实现
@SPIpublic interface SimpleExt {@Adaptive({"key1", "key2"})String yell(URL url, String s);}
对应生成extName的代码为:
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
4)办法被@Adaptive润饰,配置了value(假如两个,顺次类推),且在接口@SPI没有配置默认的实现
@SPIpublic interface SimpleExt {@Adaptive({"key1", "key2"})String yell(URL url, String s);}
对应生成extName的代码为:
String extName = url.getParameter("key1", url.getParameter("key2"));
残缺的生成类可参见附录。
2.2.4主动激活扩大类
如果你有扩大实现过Dubbo的Filter,那么肯定会对@Activate很相熟。@Activate注解的作用是能够通过给定的条件来主动激活扩大实现类,通过ExtensionLoader#getActivateExtension(URL,String, String)办法能够找到指定条件下须要激活的扩大类列表。
上面以一个例子来对@Activate的作用进行阐明,在Consumer调用Dubbo接口时,会通过生产方的过滤器链以及提供方的过滤器链,在Provider裸露服务的过程中会拼接须要应用哪些Filter。
对应源码中的地位在ProtocolFilterWrapper#buildInvokerChain(invoker, key, group)办法中。
// export:key-> service.filter ; group-> providerprivate static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) { // 在Provider裸露服务服务export时,会依据获取Url中的service.filter对应的值和group=provider来获取激活对应的Filter List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);}
ExtensionLoader#getActivateExtension(URL, String, String)是怎么依据条件来主动激活对应的扩大类列表的能够自行查看该办法的代码,此处不开展。
三、总结
本文次要对Dubbo SPI机制的扩大类加载过程通过ExtensionLoader类源码来进行总结,能够详情为以下几点:
1.Dubbo SPI联合了JDK SPI的实现,并在此基础上进行优化,如精准按需加载扩大类、缓存晋升性能。
2.剖析ExtensionLoader加载扩大类的过程,加载META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个门路下的文件,并分类缓存在ExtensionLoader实例。
3.介绍扩大包装类及其实现过程,扩大包装类实现了相似AOP的性能。
4.自适应扩大类,剖析@Adptive润饰办法时动静生成Xxx$Adaptive类的过程,以及通过参数自适应抉择扩大实现类实现办法调用的案例介绍。
简略介绍主动激活扩大类及@Activate的作用。
四、附录
4.1 Xxx$Adaptive残缺案例
@SPI接口定义
@SPI("impl1")public interface SimpleExt { // @Adaptive example, do not specify a explicit key. @Adaptive String echo(URL url, String s); @Adaptive({"key1", "key2"}) String yell(URL url, String s); // no @Adaptive String bang(URL url, int i);}
生成的Adaptive类代码
package org.apache.dubbo.common.extension.ext1; import org.apache.dubbo.common.extension.ExtensionLoader; public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt { public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("key1", url.getParameter("key2", "impl1")); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])"); org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName); return extension.yell(arg0, arg1); } public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("simple.ext", "impl1"); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])"); org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName); return extension.echo(arg0, arg1); } public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) { throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!"); } }
作者:vivo互联网服务器团队-Ning Peng