我是靠谱客的博主 听话人生,最近开发中收集的这篇文章主要介绍Dubbo SPI机制及扩展类源码详解,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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_DIRECTORYMETA-INF/services/
DUBBO_DIRECTORYMETA-INF/dubbo/
DUBBO_INTERNAL_DIRECTORYMETA-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机制及扩展类源码详解所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部