• Dubbo中的ExtensionLoader扩展(二)


    dubbo中自己实现了不同于java的SPI插件化机制,使得Dubbo可以在对多个指定的目录中加载扩展实现,同时与Java SPI不同的是可以实现按需加载。

    Dubbo的扩展SPI有如下特点:
    1. 单例,对于某个类型扩展,只会有一个ExtensionLoader;
    2. 延迟加载,可以一次只获取想要的扩展点,一次获取想要的扩展点实现;
    3. 对于扩展点的Ioc和Aop,就是一个扩展可以注入到另一个扩展中,也可以对一个扩展做wrap包装实现aop的功能;
    4. 对于扩展点的调用,真正调用的时候才能确认具体使用的是那个实现。

    源码实现

    在Dubbo加载扩展点会代码示例如下,具体实现在ExtensionLoader中。接下来会详细介绍。

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

     getExtensionLoader方法中先从缓存中拿ExtensionLoader,如果没有就new一个,new的过程如下:

        private ExtensionLoader(Class<?> type) {
            this.type = type;
            //也是利用扩展实现,后面会看到在注入其他扩展点或bean到当前扩展时使用
            objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
        }
    objectFactory就是为了获取其他扩展点而设计的,其本身也是使用ExtensionLoader来获取。具体实现看下面的TODO1
    
    

    1)ExtensionLoader#getAdaptiveExtension

    DCL初始化一个默认的自适应扩展实现,如果没有则创建一个createAdaptiveExtension。

    2)ExtensionLoader#createAdaptiveExtension

       private T createAdaptiveExtension() {
            try {
                /**
                 * 1. getAdaptiveExtensionClass:获取适配器类;
                 * 2. injectExtension:为适配器类的setter方法插入其他扩展点或实现bean。
                 */
                return injectExtension((T) getAdaptiveExtensionClass().newInstance());
            } catch (Exception e) {
                throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
            }
        }

    接下来讲解一下如何获取到扩展点的,TODO1  最后再详细解释如何在扩展点上注入其他的扩展点

    3)ExtensionLoader#getAdaptiveExtensionClass

    private Class<?> getAdaptiveExtensionClass() {
            getExtensionClasses();
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
        }

    这边会类加载所有的实现class,但是只会实例化一个cachedAdaptiveClass, TODO2 如果找不到,则dubbo通过代码生成一个自适应了扩展实现。

    4)ExtensionLoader#getExtensionClasses

     初始化该类型下所有扩展点实现class,并缓存在cachedClasses中

    5) ExtensionLoader # loadExtensionClasses

    // synchronized in getExtensionClasses
        private Map<String, Class<?>> loadExtensionClasses() {
            // SPI注解中会指定一个默认的实现   列如  interface Cluster 上  @SPI(FailoverCluster.NAME)
            // 则cachedDefaultName = FailoverCluster.NAME
            cacheDefaultExtensionName();
            // 将类型中的所有实现存放在extensionClasses中
            Map<String, Class<?>> extensionClasses = new HashMap<>();
            loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());  //META-INF/dubbo/internal/
            loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
            loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());  // //META-INF/dubbo/
            loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
            loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); // //META-INF/services/
            loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
            return extensionClasses;
        }

    6 ExtensionLoader#loadDirectory -> ExtensionLoader#loadResource -> ExtensionLoader#loadClass

    主要是找到加载器,然后读取文件,忽略注释,通过class.forName来类加载具体实现。最后保存在extensionClasses。其中自适应的实现和包装类的实现class,单独赋值。

        private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
            //必须是该扩展点的实现
            if (!type.isAssignableFrom(clazz)) {
                throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                        type + ", class line: " + clazz.getName() + "), class "
                        + clazz.getName() + " is not subtype of interface.");
            }
            //这里判断是否有自定义的适配器类,如果有,后面获取适配器的时候,就可以直接用这个创建返回,不用dubbo动态创建
            if (clazz.isAnnotationPresent(Adaptive.class)) {
                cacheAdaptiveClass(clazz);
            } else if (isWrapperClass(clazz)) {
                // 包装类会有一个参数为type的构造器
                cacheWrapperClass(clazz);
            } else { //处理不是包裹类的情况 且 又不带有Adaptive注解
                clazz.getConstructor();
                if (StringUtils.isEmpty(name)) {
                    // 计算出实现类的name  列如
                    // clazz = org.apache.dubbo.common.compiler.support.JdkCompiler.class
                    //  type = org.apache.dubbo.common.compiler.Compiler.class
                    // 则 name = JdkCompiler - Compiler  = jdk
                    name = findAnnotationName(clazz);
                    if (name.length() == 0) {
                        throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                    }
                }
    
                String[] names = NAME_SEPARATOR.split(name);
                if (ArrayUtils.isNotEmpty(names)) {
                    //判断下Activate注解,后面讲,这里只需要知道,会缓存Activate注解的实现
                    cacheActivateClass(clazz, names[0]);
                    for (String n : names) {
                        cacheName(clazz, n);
                        //缓存扩展实现
                        saveInExtensionClass(extensionClasses, clazz, name);
                    }
                }
            }
        }

    这边不得不强调一下,关于Adaptive注解。这个注解可以修饰具体的扩展实现类,也可以修饰具体扩展的实现类中的方法。

    当修饰的是类的时候,将作为默认的扩展点,如果是修饰SPI的方法的时候,将会生成类的Methed$Adaptive中重新该方法,即上面的TODO2的内容。

    另外还缓存了Activate类,关于Activate类将会后续介绍 TODO3

    整个ExtensionLoader的流程相对是比较简单的,无非就是使用到@SPI注解的值或者是@Adaptive自定义的扩展。接下来具体讲解下上面流程中提到的TODO。

    TODO1   

    实现IOC,一个扩展中初始化另一个扩展, ExtensionLoader#injectExtension

    /** 插入该扩展需要其他扩展或bean */
        private T injectExtension(T instance) {
            try {
                if (objectFactory != null) {  //   默认objectFactory =  AdaptiveExtensionFactory
                    for (Method method : instance.getClass().getMethods()) {
                        if (isSetter(method)) {
                            /**
                             * Check {@link DisableInject} to see if we need auto injection for this property
                             */
                            if (method.getAnnotation(DisableInject.class) != null) {
                                continue;
                            }
                            Class<?> pt = method.getParameterTypes()[0];
                            // 过滤掉基础类型
                            if (ReflectUtils.isPrimitives(pt)) {
                                continue;
                            }
                            try {
                                // for instance: setVersion, return "version"
                                String property = getSetterProperty(method);
                                // 根据参数类型class, 以及方法名中获取的property,通过具体的ExtensionFactory来获取到具体的扩展点
                                Object object = objectFactory.getExtension(pt, property);
                                if (object != null) {
                                    // 将扩展实现作为setter方法的参数,来实现注入
                                    method.invoke(instance, object);
                                }
                            } catch (Exception e) {
                                logger.error("Failed to inject via method " + method.getName()
                                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
            return instance;
        }

    其中AdaptiveExtensionFactory实际上就是加载所有的非Adaptive的扩展点,然后将其实例保存在List<ExtensionFactory> factories中,当需要获取指定name和type的Extension上的时候,实际上就是遍历所有的ExtensionFactory,直到加载到对应的扩展点。列如需要注入的扩展,即setter方法的参数类型也是注解了SPI,那么就会默认使用到SPIExtensionFactory,即自适应加载。

    @Adaptive
    public class AdaptiveExtensionFactory implements ExtensionFactory {
    
        private final List<ExtensionFactory> factories;
    
        public AdaptiveExtensionFactory() {
            ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
            List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
            //  获取到可以支持的哪些具体实现
            for (String name : loader.getSupportedExtensions()) {
                // 对所有的ExtensionFactory扩展点  实例化,  实际上就一个SpiExtensionFactory
                list.add(loader.getExtension(name));
            }
            factories = Collections.unmodifiableList(list);
        }
    
        @Override
        public <T> T getExtension(Class<T> type, String name) {
            for (ExtensionFactory factory : factories) {
                T extension = factory.getExtension(type, name);
                if (extension != null) {
                    return extension;
                }
            }
            return null;
        }

     TODO2

    如果所有的SPI扩展实现都没有找到@Adaptive的扩展,那么dubbo就会生成自适应扩展的代码。根据SPI接口会重写@Adaptive注解的方法,方法参数一定是能够获取到URL的。如果在URL获取不到扩展名,则默认取SPI注解指定的值,然后用这个扩展名,通过ExtensionLoad加载对应的扩展点。最后通过编译器来编译code,加载到$Adaptive结尾的class,编译器也有多种实现,也是通过ExtensionLoad加载

        /** Dubbo生成适配类 */
        private Class<?> createAdaptiveExtensionClass() {
            //动态生成适配器代码
            String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
            ClassLoader classLoader = findClassLoader();
            org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
            return compiler.compile(code, classLoader);
        }

    这边我们做一个简单的测试,看一下自动生成自适应代码是什么样的。

        public interface Compiler {
    
            /**
             * Compile java source code.
             *
             * @param code        Java source code
             * @param classLoader classloader
             * @return Compiled class
             */
    
            Class<?> compile(String code, ClassLoader classLoader);
    
            @Adaptive({"p1", "p2"})
            Validator getValidator(URL url);
        }
    
        public static void main(String[] args) {
            String c = new AdaptiveClassCodeGenerator(org.apache.dubbo.common.extension.ExtensionLoader.Compiler.class, "jdk").generate();
            System.out.println(c);
        }

    这个测试做的事情就是加载一个org.apache.dubbo.common.extension.ExtensionLoader.Compiler.class的实现,但是由于没有找到任何的扩展点,所以就采用自动生成code,编译的方式。

    其结果如下:

    package org.apache.dubbo.common.extension;
    import org.apache.dubbo.common.extension.ExtensionLoader;
    
    public class Compiler$Adaptive implements org.apache.dubbo.common.extension.ExtensionLoader.Compiler {
        public java.lang.Class compile(java.lang.String arg0, java.lang.ClassLoader arg1) {
            throw new UnsupportedOperationException("The method public abstract java.lang.Class org.apache.dubbo.common.extension.ExtensionLoader$Compiler.compile(java.lang.String,java.lang.ClassLoader) of interface org.apache.dubbo.common.extension.ExtensionLoader$Compiler is not adaptive method!");
        }
    
        public javax.xml.validation.Validator getValidator(org.apache.dubbo.common.URL arg0) {
            if (arg0 == null) throw new IllegalArgumentException("url == null");
            org.apache.dubbo.common.URL url = arg0;
            String extName = url.getParameter("p1", url.getParameter("p2", "jdk"));
            if (extName == null)
                throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ExtensionLoader$Compiler) name from url (" + url.toString() + ") use keys([p1, p2])");
            org.apache.dubbo.common.extension.ExtensionLoader$Compiler extension = (org.apache.dubbo.common.extension.ExtensionLoader$Compiler) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ExtensionLoader$Compiler.class).getExtension(extName);
            return extension.getValidator(arg0);
        }
    }

    其特点如下:

    1) 生成一个$Adaptive后缀的class

    2)  如果没有@Adaptive注解的方法,则throw一个UnsupportedOperationException

    3)对于使用的Adaptive注解的方法,其参数列表一定需要一个URL类型的参数,根据上面的配置,从URL中获取P1是值,如果获取不到则获取P2的值,如果再获取不到,则使用默认的值

    4)根据第三步获取的值,从ExtensionLoader加载指定的扩展点实现。

    TODO3 

    先来看一下@Activate注解

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface Activate {
        /**
         * Group过滤条件。
         * <br />
         * 包含{@link ExtensionLoader#getActivateExtension}的group参数给的值,则返回扩展。
         * <br />
         * 如没有Group设置,则不过滤。
         */
        String[] group() default {};
    
        /**
         * Key过滤条件。包含{@link ExtensionLoader#getActivateExtension}的URL的参数Key中有,则返回扩展。
         * <p />
         * 示例:<br/>
         * 注解的值 <code>@Activate("cache,validatioin")</code>,
         * 则{@link ExtensionLoader#getActivateExtension}的URL的参数有<code>cache</code>Key,或是<code>validatioin</code>则返回扩展。
         * <br/>
         * 如没有设置,则不过滤。
         */
        String[] value() default {};
    
        /**
         * 排序信息,可以不提供。
         */
        String[] before() default {};
    
        /**
         * 排序信息,可以不提供。
         */
        String[] after() default {};
    
        /**
         * 排序信息,可以不提供。
         */
        int order() default 0;
    }

    group基本表示用在服务端还是消费端,value表示激活这个扩展点的条件,before、after、order用于排序。为了加深理解,我们拿一个UT例子看一下,

    getActivateExtension方法基本都会传入url作为参数,用法是获取激活条件的所有扩展点实现类。

    @Test
        public void testLoadDefaultActivateExtension() throws Exception {
            // test default
            URL url = URL.valueOf("test://localhost/test?ext=order1,default");
            List<ActivateExt1> list = ExtensionLoader.getExtensionLoader(ActivateExt1.class)
                    .getActivateExtension(url, "ext", "default_group");
            Assertions.assertEquals(2, list.size());
            Assertions.assertTrue(list.get(0).getClass() == OrderActivateExtImpl1.class);
            Assertions.assertTrue(list.get(1).getClass() == ActivateExt1Impl1.class);
    
            url = URL.valueOf("test://localhost/test?ext=default,order1");
            list = ExtensionLoader.getExtensionLoader(ActivateExt1.class)
                    .getActivateExtension(url, "ext", "default_group");
            Assertions.assertEquals(2, list.size());
            Assertions.assertTrue(list.get(0).getClass() == ActivateExt1Impl1.class);
            Assertions.assertTrue(list.get(1).getClass() == OrderActivateExtImpl1.class);
        }
    
    
    
    扩展配置如下:
    group=org.apache.dubbo.common.extension.activate.impl.GroupActivateExtImpl
    value=org.apache.dubbo.common.extension.activate.impl.ValueActivateExtImpl
    order1=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl1
    order2=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl2
    old1=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl2
    old2=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl3

    接下里看一下ExtensionLoader#getActivateExtension

    //获取满足激活条件的扩展实现
    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();
            //上一步会缓存所有Active注解的实现类到cachedActivates
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                //匹配group,provider还是consumer
               //  activateGroup = activate扩展点注解activate配置的Group 
    // group URL中的group
                if (isMatchGroup(group, activate.group())) {
                    //获取扩展实现类
                    T ext = getExtension(name);
                    //isActive匹配激活条件
                    if (! names.contains(name)
                            && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                            && isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            //这里会重新排序,用到Active里面before、after和order
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        ....
        ....
        return exts;
    }

     就是说根据URL,会把需要的扩展都会收集起来,并排好序。

  • 相关阅读:
    jQuery中$.each()方法的使用
    点击分享功能
    localStorage使用总结
    核桃说笔记2
    php 微信模板消息发送
    报错:SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated UXXXXXXXX escape
    报错:Cannot remove entries from nonexistent file c:program filesanaconda3libsite-packageseasy-install.pth
    Pycharm如何修改背景图(BackgroundColor)
    git 设置 .gitignore 为全局global + 配置.gitignore为全局后不生效解决办法
    Windows Server 2016 下执行定时任务(英文系统)
  • 原文地址:https://www.cnblogs.com/gaojy/p/15685696.html
Copyright © 2020-2023  润新知