概述
SPI是什么
SPI全称:service provider interface,它可以动态的获取到接口的实现类。
在Dubbo中 自己实现了一套SPI机制,Java也有原生的SPI机制,下面看看Java和Dubbo的如何使用,有什么区别。
SPI如何使用
-
Java SPI使用
//定义接口 public interface Test{ void test(); } //创建实现类 public class Test1Impl implements Test{ public void test(){ System.out.println("Test1Impl"); } } //创建实现类 public class Test2Impl implements Test{ public void test(){ System.out.println("Test2Impl"); } }
//定义配置文件 在resource中创建META-INF目录,在该目录下创建services文件夹。 再以Test接口的全路径包名为文件名称进行文件创建:比如com.xxxx.Test 文件内容为改接口的具体实现类:比如 com.xxxx.impl.Test1Impl com.xxxx.impl.Test2Impl 多个实现类则换行分割。
//创建启动类 public class TestMain(){ public static void main(String []args){ ServiceLoader<Test> services = ServiceLoader.load(Test.class); //加载该接口的实现类 for(Test test : services){ test.test();//执行两个实现类具体的方法 } } }
以上就是Java SPI的使用,可以发现ServiceLoader会加载所有的实现类,并且进行初始化,如果太多扩展实现类的话初始化则很耗时,没有用上的也会进行加载,浪费资源。
-
Dubbo SPI使用
Dubbo 中对 Java SPI进行了改进,增加了IOC和AOP的支持,可以通过setter直接注入其它的扩展实现类,而且以上的JavaSPI中,ServiceLoader会一次加载所有的实现类并且初始化。DubboSPI中只会加载配置文件中的类,按类型进行缓存(类型可分为普通扩展类、包装扩展类、自适应扩展类),但是不会进行初始化,所有再性能上有更好的优势。使用Dubbo SPI
复用上面的Java SPI代码//定义接口 @SPI("impl1") //指定默认的SPI实现。 public interface Test{ void test(); } //创建实现类 public class Test1Impl implements Test{ public void test(){ System.out.println("Test1Impl"); } } //创建实现类 public class Test2Impl implements Test{ public void test(){ System.out.println("Test2Impl"); } }
定义配置文件 在resource/META-INF/dubbo/internal目录下以Test接口全路径包名为文件名称创建文件。 内容为key=value的形式,多个换行分割。可以根据key进行获取。比如: impl1=com.xxxx.impl.Test1Impl impl2=com.xxxx.impl.Test2Impl
//创建启动类 public class TestMain(){ public static void main(String []args){ //获取Test接口的ExtensionLoader扩展类加载器,并且调用默认的扩展类实现。 Test test = ExtensionLoader .getExtesionLader(Test.class).getDefaultExtension()//默认的就是SPI注解中的值对应的配置文件中的key所对应的类。 test.test();//这里是Test1Impl //也可以根据key去获取 Test tes2 = ExtensionLoader .getExtesionLader(Test.class).getExtension("impl2"); //这里获取的就是impl2这个key对应的class。 } }
扩展点分类及其缓存
Dubbo中扩展类有:
- 普通扩展:标注了SPI注解的,在配置文件中定义了的。
- 包装类扩展:这种wrapper包装类,它也实现接口,并且还有还改接口的扩展类型作为成员变量。提供构造方法,参数为该接口类型,Dubbo会自动注入。比如dubbo中的ProtocolFilerWrapper类
public class ProtocolFilterWrapper implements Protocol { //实现Protocl private final Protocol protocol; public ProtocolFilterWrapper(Protocol protocol) {//传入一个protocol if (protocol == null) { throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }
- 自适应类扩展: dubbo中一个扩展接口可能会有多个实现,具体的实现不用写死在代码中,可以通过@Adaptive注解标志。它会从传入的URL参数中动态的获取具体的实现类。
缓存可分为class缓存与实例缓存:
- class缓存:dubbo spi获取扩展类时,会优先从缓存中获取,如果缓存未命中,则加载配置文件中的class到内存中进行缓存,等待初始化时使用。
- 实例缓存:将实例化好的对象,进行缓存,下次直接取用内存中的。
这就是dubbo为什么比JavaSPI性能好的原因之一。
原理实现
普通扩展类的实现原理。就是标注了SPI注解的。
主要就是通过ExtensionLoader这个类来实现的。
ExtensionLoader 实现原理,主要有三个方法,对应三个分类:
getExtension,getAdaptiveExtension,getActivateExtension。
加载普通扩展类原理:
从getExtension(“name”)开始
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) { //如果getExtension("true")
return getDefaultExtension(); //返回默认的扩展类 如:SPI("Netty") 找到配置文件位netty=xxx的实现类
}
Holder<Object> holder = cachedInstances.get(name); //从缓存中获取
if (holder == null) { //空的则进行put
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();//获取实例
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);//都是空的则创建扩展类
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension():中会首先调用getExtensionClasses进行加载
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();//都是空的则加载class
cachedClasses.set(classes);
}
}
}
return classes;
}
getExtensionClasses方法中先从缓存中获取,是空的则调用loadExtensionClasses()加载
loadExtensionClasses():
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);//获取该接口上的SPI注解
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];//默认实现类的名字
}
}
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注解中的值,将值设置为默认实现名称。
最后掉哦那个loadDirectory进行配置文件的解析有三个路径,分别是:
名称 | 值 |
---|---|
SERVICES_DIRECTORY | META-INF/services/ |
DUBBO_DIRECTORY | META-INF/dubbo/ |
DUBBO_INTERNAL_DIRECTORY | META-INF/dubbo/internal/ |
首先加载的DUBBO_INTERNAL_DIRECTORY下的配置文件
进入到loadDirectory()方法:
//loadDirectory
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
//通过getResources或者getSystemResources得到配置文件
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); //
}
}
loadDirectory首先获取到classLoader,再通过getResources或者getSystemResources得到配置文件,然后得到全路径,最后调用loadResource进行文件内容解析。
loadResource()方法会解析出每一行,进行loadClass
loadClass()是关键:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
····异常省略
if (clazz.isAnnotationPresent(Adaptive.class)) { //判断是不是标注了Adaptive注解
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
}
} else if (isWrapperClass(clazz)) { //判断是不是wrapper包装类,如果构造函数是改接口类型的话就是包装类
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
····异常省略
}
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); //加入普通扩展类
}
····异常省略
}
}
}
}
loadClass方法主要进行分类,首先如果改扩展实现类(传入的clazz是配置文件定义的实现类)包含了@Adaptive注解的话就将其赋值给cachedAdaptiveClass缓存,这个缓存只会存在一个。也就是说接口的实现类上只能有一个标记了@Adaptive注解。
然后在判断是否是wrapper包装类。根据构造函数的参数进行判断。看这个构造参数类型是不是对应的接口类型,如果是则加入到对应的缓存中。
再就是判断是否标记了@Activate注解。如果标记了也加入到对应的缓存。
最后则是普通扩展类,加入到extensionClasses中。
然后调用栈结束,方法放回到createExtension方法:
private T createExtension(String name) {
//getExtensionClasses()这里就是上面的调用,最后返回的是extensionClasses集合
//里面存储了普通扩展类的实现class,然后根据name进行获取,比如name=impl1
Class<?> clazz = getExtensionClasses().get(name);
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); //如果扩展类是空的话则进行实例化,并且存入
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);//注入扩展类依赖的属性 set开头的方法
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//如果是wrapper类的话,、
// 将wrapper进行初始化,并且传入改扩展类到wrapper类的构造方法中,实例化进行返回
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
}
}
createExtension()主要的就是加载配置文件里的扩展类添加到集合,然后根据名称获取,进行初始化,属性注入,在判断有不有wrapper包装类,有的话就调用wrapper的构造方法,参数为改接口类型进行实例化,再讲改扩展类进行注入到wrapper包装类中。最后返回:如果是包装类就返回包装类,如果是普通类就返回普通类。包装类里包装的就是普通扩展类;以上就是整个普通扩展类和包装类的获取。
@Adaptive注解标志的扩展点(自适应)
主要入口是通过getAdaptiveExtension()方法。如果有实现类标注了@Adaptive注解的话,该类作为默认的实现类进行返回,如果标记在接口的方法上,并且没有实现类标注该注解,则会为该类生成一个字符串类,类名为:接口类名+$Adaptive,并且实现接口上的方法,通过参数列表的URL动态获取具体的实现类。通过@Adaptive({“key1”,”key2“}) 中的key1找到URL上的key1=impl1。比如:
@SPI("impl1")
public interface SimpleExt {
// @Adaptive example, do not specify a explicit key.
@Adaptive
String echo(URL url, String s);
//查找参数列表URL中对应的key 比如url为:
//localhost:8080/user?impl2=impl,如果url中没有第一个key,就找第二个key去获取对应的实现类。都没有就用当前类名生成:simple.ext
@Adaptive({"impl2", "test"})
String yell(URL url, String s);
// no @Adaptive
String bang(URL url, int i);
}
看源码了解上面介绍,进入到源码getAdaptiveExtension中:
//省略了异常单吗
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) {
}
}
}
}
}
return (T) instance;
}
这个方法中,首先从cachedAdaptiveInstance缓存中获取创建好的实例,看有不有,如果没有则创建,这里是双重检测,进行加锁,防止重复创建。如果都为命中的话,则调用createAdaptiveExtension()方法进行创建。
private T createAdaptiveExtension() {
Class<?> adaptiveExtensionClass = getAdaptiveExtensionClass();
Object instance = adaptiveExtensionClass.newInstance();
return injectExtension((T) instance);
}
进入到getAdaptiveExtensionClass()方法中:
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();//这个方法上面讲解过了,大概就是加载配置文件进行class分类,如果找到了标注@Adaptive注解的类,cachedAdaptiveClass 就不为空,这个class则就是默认实现。
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();//如果没有找到标注了Adaptive注解的类则创建标志了方法的
}
如果没有找到,则进入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这个方法很长,但是大致目标却很简单。它会遍历标记了Adaptive注解的方法,然后生成具提的实现类。比如生成下面这个:
package com.alibaba.dubbo.common.extensionloader.ext1;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adaptive implements com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt {
public java.lang.String echo(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("simple.ext", "impl1");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) name from url(" + url.toString() + ") use keys([simple.ext])");
com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt extension = (com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
public java.lang.String yell(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("impl2", url.getParameter("test", "impl1"));
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) name from url(" + url.toString() + ") use keys([impl2, test])");
com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt extension = (com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.class).getExtension(extName);
return extension.yell(arg0, arg1);
}
public java.lang.String bang(com.alibaba.dubbo.common.URL arg0, int arg1) {
throw new UnsupportedOperationException("method public abstract java.lang.String com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.bang(com.alibaba.dubbo.common.URL,int) of interface com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt is not adaptive method!");
}
}
这个SimpleExt$Adaptive类就是createAdaptiveExtensionClassCode方法生成的字符串,讲所有的方法进行拼接,提取出@Adaptive中定义的key,再去URL中获取key对应的value,讲方法生成好,拼接成字符串。最后通过Compiler进行编译,得到可执行的实例。
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class)
.getAdaptiveExtension();
return compiler.compile(code, classLoader);
Compiler也是一个扩展接口,它标注了@SPI(“javasist”),但它的实现类AdaptiveCompiler标注了@Adaptive的接口,所以它是默认获取到的Adaptive,上面代码中getAdaptiveExtension()返回的是AdaptiveCompiler类,调用compile方法后在这里面又会获取到它默认的实现类JavassistCompiler,最终调用的是JavassistCompiler.compile方法得到可执行对象返回。
以上是整个getAdaptiveExtension()的整个流程。
@Activate扩展(自动激活)
最后@Activate注解效果也是一样的,只不过进行了分组,例如:
@Activate(group = {“group1”, “group2”}),表示如果URL中带有group=”group1“,或者group=”group2“,则就激活,添加到缓存。提供使用。
以上就是dubbo的扩展点以及ExtensionLoader的加载原理。
getExtension()方法是其它两个方法的基石。
源码是基于2.6.x的,2.7.x大同小异,3.0的暂时未看。
最后
以上就是听话人生为你收集整理的Dubbo SPI机制及扩展类源码详解的全部内容,希望文章能够帮你解决Dubbo SPI机制及扩展类源码详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复