• dubbo是如何实现可扩展的?


    dubbo如何实现可扩展的,援引官网描述

    Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

    Dubbo 改进了 JDK 标准的 SPI 的以下问题:

    • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
    • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
    • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

     

    定义个接口:

    public interface HelloService {
        String  sayHello();
    }

    定义两个实现类:

    复制代码
    public class DogHelloService  implements HelloService {
        @Override
        public String sayHello() {
            return "wang";
        }
    }
    复制代码
    复制代码
    public class HumanHelloService   implements HelloService {
        @Override
        public String sayHello() {
            return "hello 你好";
        }
    }
    复制代码

     

    1.JDK标准的SPI是怎么回事?

    ServiceLoader.load方法会加载META-INF/services/目录下定义的接口全限定名文件,内容为实现类。

    com.exm.service.impl.DogHelloService
    com.exm.service.impl.HumanHelloService

     

    核心为ServiceLoader.LazyIterator迭代器,在load方法被调用时,会初始化该迭代器,如下:

     public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }

    LazyIterator会读取配置实现类,并通过反射进行实例化(前提要求实现类需要具备无参构造)。

    其中hasNextService方法会加载META-INF/services接口文件,并加载到Enumeration configs中,源码如下:

    复制代码
    private boolean hasNextService() {
                if (nextName != null) {
                    return true;
                }
                if (configs == null) {
                    try {
                          //获取配置全路径名。如:META-INF/services/com.exm.service.HelloService
                        String fullName = PREFIX + service.getName();
                        if (loader == null)
                            configs = ClassLoader.getSystemResources(fullName);
                        else
                          //并加载到Enumeration configs中
                            configs = loader.getResources(fullName);
                    } catch (IOException x) {
                        fail(service, "Error locating configuration files", x);
                    }
                }
                while ((pending == null) || !pending.hasNext()) {
                    if (!configs.hasMoreElements()) {
                        return false;
                    }
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();
                return true;
            }
    复制代码

    迭代器通过nextService获取下一个实现类对象,源码如下,其中包含反射拿到Class对象,并实例化。

    复制代码
    private S nextService() {
                if (!hasNextService())
                    throw new NoSuchElementException();
                String cn = nextName;
                nextName = null;
                Class c = null;
                try {
                  //反射实例化
                    c = Class.forName(cn, false, loader);
                } catch (ClassNotFoundException x) {
                    fail(service,
                         "Provider " + cn + " not found");
                }
                if (!service.isAssignableFrom(c)) {
                    fail(service,
                         "Provider " + cn  + " not a subtype");
                }
                try {
                  //转强制化为接口,并放入LinkedHashMap providers中
                    S p = service.cast(c.newInstance());
                    providers.put(cn, p);
                    return p;
                } catch (Throwable x) {
                    fail(service,
                         "Provider " + cn + " could not be instantiated",
                         x);
                }
                throw new Error();          // This cannot happen
            }
    复制代码

    为什么说JDK的SPI会一次性加载并实例化所有的扩展呢?

    因为在调load时,实际构造了ServiceLoader.LazyIterator,如果想找到某个扩展实现,需要迭代器遍历所有的实现才可以。

    1⃣️如果其中有一个实例化或cast时异常,后边所有都将无法遍历。

    2⃣️如果某个类的实例化耗时很长,并没用到,会造成资源浪费

     

    编写一个测试方法:

    复制代码
    public static void main(String[] args) {
            final ServiceLoader helloServices  = ServiceLoader.load(HelloService.class);
            for (HelloService helloService : helloServices){
                System.out.println(helloService.getClass().getName() + ":" + helloService.sayHello());
            }
        }
    复制代码

    测试输出:

    com.exm.service.impl.DogHelloService:wang
    com.exm.service.impl.HumanHelloService:hello 你好

     

    2.Dubbo是怎么进行改进的呢?

    dubbo时如何进行改进的呢?

    (1)dubbo定义的SPi文件包含了key,即每个实现类对应一个不同的key,在加载class的时候,会将key和class放入一个map中。

    这样在使用者想使用哪个类的实例时,只需要实例化对应的类,无需实例化所有类

    (2)Adaptive功能:实现动态的使用扩展点。通过 getAdaptiveExtension方法 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。

     

    2.1 加载所有扩展点,选择性实例化

    定义接口:

    复制代码
    @SPI("human")
    public interface HelloService {
        String  sayHello();
        @Adaptive
        String  sayHello(URL  url);
    }
    复制代码

    加载扩展点

    复制代码
    public class Main {
        public static void main(String[] args) {
            // 获取扩展加载器
            ExtensionLoader  extensionLoader  = ExtensionLoader.getExtensionLoader(HelloService.class);
            // 遍历所有的支持的扩展点,并将key与实现类进行关联
            Set  extensions = extensionLoader.getSupportedExtensions();
            for (String extension : extensions){
                String result = extensionLoader.getExtension(extension).sayHello();
                System.out.println(result);
            }
        }
    }
    复制代码

    ExtensionLoader.getSupportedExtensions会加载所有扩展类(但没有实例化)。然后通过extensionLoader.getExtension对指定key进行实例化,这一点是与JDK不同的。

    我们看下具体是怎么加载的,getSupportedExtensions为入口,最终会通过loadDirectory进行加载

    Set getSupportedExtensions()
        Map> getExtensionClasses()
            Map> loadExtensionClasses()
                void loadDirectory(Map> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst)

    (1)getSupportedExtensions方法会返回所有扩展点的key,供用户使用。

    public Set getSupportedExtensions() {
            Map> clazzes = this.getExtensionClasses();
                  //获取到所有扩展点后,将key放入TreeSet中(按字符串排序)
            return Collections.unmodifiableSet(new TreeSet(clazzes.keySet()));
        }

    loadDirectory加载文件的来源为以下6个部分,兼容了JDK路径。

    同时加载有顺序,越靠前越优先加载

    复制代码
    private Map> loadExtensionClasses() {
            this.cacheDefaultExtensionName();
            Map> extensionClasses = new HashMap();
            this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName(), true);
            this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName().replace("org.apache", "com.alibaba"), true);
            this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName());
            this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName().replace("org.apache", "com.alibaba"));
            this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName());
            this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName().replace("org.apache", "com.alibaba"));
            return extensionClasses;
        }
    复制代码

    (2)在Set中获取到扩点类对应的key,通过getExtension获取对应class的实例(包含通过setter进行依赖注入)

    复制代码
    public T getExtension(String name) {
            if (StringUtils.isEmpty(name)) {
                throw new IllegalArgumentException("Extension name == null");
            } else if ("true".equals(name)) {
                return this.getDefaultExtension();
            } else {
                Holder holder = this.getOrCreateHolder(name);
                Object instance = holder.get();
                if (instance == null) {
                    synchronized(holder) {
                        instance = holder.get();
                        if (instance == null) {
                              //创建对应class的实例,完成依赖注入
                            instance = this.createExtension(name);
                            holder.set(instance);
                        }
                    }
                }
    
                return instance;
            }
        }
    
    复制代码

    createExtension方法是实例化的核心,实现了IOC和AOP,注释如下:

    复制代码
    private T createExtension(String name) {
             //获取name对应的class
            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, clazz.newInstance());
                    instance = (T) EXTENSION_INSTANCES.get(clazz);
                }
                  //依赖注入(IOC)
                injectExtension(instance);
                  //包装器(AOP)
                Set> wrapperClasses = cachedWrapperClasses;
                if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                    for (Class wrapperClass : wrapperClasses) {
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
                initExtension(instance);
                return instance;
            } catch (Throwable t) {
                throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                        type + ") couldn't be instantiated: " + t.getMessage(), t);
            }
        }
    复制代码

     

     2.2 getAdaptiveExtension根据URL参数动态获取相应的扩展点

    复制代码
    public class AdaptiveMain {
        public static void main(String[] args) {
            URL   url  = URL.valueOf("test://localhost/hello?hello.service=dog");
            HelloService  adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
            String  msg = adaptiveExtension.sayHello(url);
            System.out.println(msg);
        }
    }
    复制代码

    (1)核心代码为ExtensionLoader.getAdaptiveExtension方法

    复制代码
    public T getAdaptiveExtension() {
            Object instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                if (createAdaptiveInstanceError != null) {
                    throw new IllegalStateException("Failed to create adaptive instance: " +
                            createAdaptiveInstanceError.toString(),
                            createAdaptiveInstanceError);
                }
    
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                              //创建自适应扩展
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
    
            return (T) instance;
        }
    复制代码

    (2)创建自适应扩展类实例

    复制代码
    private T createAdaptiveExtension() {
            try {
                  //获取自适应扩展类,并实例化,然后通过setter注入依赖
                return injectExtension((T) getAdaptiveExtensionClass().newInstance());
            } catch (Exception e) {
                throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
            }
        }
    复制代码

    (3)生成自适应扩展类class

    复制代码
    private Class getAdaptiveExtensionClass() {
            getExtensionClasses();
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
        }
    复制代码

    (4)加载类扩展点(与上文相同)

    复制代码
    private Map> getExtensionClasses() {
            Map> classes = cachedClasses.get();
            if (classes == null) {
                synchronized (cachedClasses) {
                    classes = cachedClasses.get();
                    if (classes == null) {
                        classes = loadExtensionClasses();
                        cachedClasses.set(classes);
                    }
                }
            }
            return classes;
        }
    复制代码

    (5)创建自适应扩展class,动态生成代码,并进行编译

    复制代码
    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);
    }
    复制代码

    得到如下class:

    复制代码
    package com.exm.service;
    import org.apache.dubbo.common.extension.ExtensionLoader;
    public class HelloService$Adaptive implements com.exm.service.HelloService {
    public java.lang.String sayHello()  {
    throw new UnsupportedOperationException("The method public abstract java.lang.String com.exm.service.HelloService.sayHello() of interface com.exm.service.HelloService is not adaptive method!");
    }
    public java.lang.String sayHello(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("hello.service", "human");
    if(extName == null) throw new IllegalStateException("Failed to get extension (com.exm.service.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
    com.exm.service.HelloService extension = (com.exm.service.HelloService)ExtensionLoader.getExtensionLoader(com.exm.service.HelloService.class).getExtension(extName);
    return extension.sayHello(arg0);
    }
    }
    复制代码

    可以看到自适应只支持具有adaptive注解的方法,并且参数汇总需要有URL参数。

    具体逻辑,是通过获取URL参数中的变量,ExtensionLoader.getExtensionLoader().getExtension(name)获取具体的class实例,完成调用。

    (6)通过setter注入依赖

    复制代码
    private T injectExtension(T instance) {
    
        if (objectFactory == null) {
            return instance;
        }
    
        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }
                /**
                 * 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 {
                    String property = getSetterProperty(method);
                      //获取到Adaptive对象
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        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;
    }
    复制代码

    (7)上文中的objectFactory是ExtensionFactory实例,其实现类包含SpiExtensionFactory和SpringExtensionFactory,一个是dubbo的扩展工厂,一个是Spring的工厂;前者只支持type是SPI的接口,并生成自适应类;后者从Spring容器中获取。

    在依赖注入时,会在两个容器中遍历,如下:

    复制代码
    public  T getExtension(Class type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
    复制代码

     

    (8)另外,还有一个实现类是AdaptiveExtensionFactory是默认的@Adaptive类,即被该注解修饰的类是自适应类,就不会动态生成了。

    在getExtensionClasses()加载ExtensionFactory扩展class时,如果扩点类被Adaptive注解修饰,则将缓存在ExtensionLoader.cachedAdaptiveClass中;

    在getAdaptiveExtensionClass方法中,直接返回,不需要生成自适应类

    复制代码
    private void loadClass(Map> 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.");
        }
          //判断类事都是Adaptive类,是的话就缓存,在getAdaptiveExtensionClass时直接返回
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                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)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }
    复制代码

     

    综上,dubbo加载class扩展与实例化是分开的,可以通过指定key实例化某一个class;

    dubbo支持IOC和AOP;

    同时,dubbo结合SPI与Adaptive注解,可以实现对所有扩展class封装,然后根据URL参数动态获取指定的class。

     

    3.在注入依赖的时候是否有循环依赖的问题?

    在dubbo创建扩展class实例时,会通过setter进行依赖注入,如果存在循环依赖,怎么处理?

    在dubbo依赖注入时,除了Spring容器外,从SPI容器中获取,获取的是SPI接口的自适应实现,是新创建的类,所以不存在循环依赖的问题。

     

    牛逼的框架,就是让你一眼看不懂它在干什么   ---me

  • 相关阅读:
    单元测试Testng
    HSV色彩空间 GRAY色彩空间
    C++的一些好用的限定修饰符
    贪心算法-翻硬币
    Redis -- 基础知识1
    java8之CompletableFuture
    【XR806开发板试用】XR806简单使用GPIO命令通过继电器远程控制其它开发板
    前任开发在代码里下毒了,支付下单居然没加幂等
    二分查找汇总
    排序算法图解(六):归并排序
  • 原文地址:https://www.cnblogs.com/shuimuzhushui/p/16315168.html