dubbo是如何实现可扩展的?

博客 分享
0 232
张三
张三 2022-05-26 21:59:43
悬赏:0 积分 收藏

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.DogHelloServicecom.exm.service.impl.HumanHelloService

 

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

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

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

其中hasNextService方法会加载META-INF/services接口文件,并加载到Enumeration<URL> 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<URL> 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<String,S> 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<HelloService> helloServices  = ServiceLoader.load(HelloService.class);        for (HelloService helloService : helloServices){            System.out.println(helloService.getClass().getName() + ":" + helloService.sayHello());        }    }

测试输出:

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

 

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

dubbo时如何进行改进的呢?

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

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

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

 

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

public class Main {    public static void main(String[] args) {        // 获取扩展加载器        ExtensionLoader<HelloService>  extensionLoader  = ExtensionLoader.getExtensionLoader(HelloService.class);        // 遍历所有的支持的扩展点,并将key与实现类进行关联        Set<String>  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<String> getSupportedExtensions()    Map<String, Class<?>> getExtensionClasses()        Map<String, Class<?>> loadExtensionClasses()            void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst)

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

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

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

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

private Map<String, Class<?>> loadExtensionClasses() {        this.cacheDefaultExtensionName();        Map<String, Class<?>> 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<Object> 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<Class<?>> 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<String, Class<?>> getExtensionClasses() {        Map<String, Class<?>> 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> T getExtension(Class<T> 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<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.");    }      //判断类事都是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

posted @ 2022-05-26 21:44 水木竹水 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    张三

    张三 (王者 段位)

    821 积分 (2)粉丝 (41)源码

     

    温馨提示

    亦奇源码

    最新会员