dubbo SPI @Adaptive源码解读

76 阅读5分钟

接上文dubbo SPI @Adaptive注解使用方法与原理解析

@TOC

概览

自适应类的用法示例

SimpleExt simpleExt= ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();

接口代码示例

@SPI("impl1")
public interface SimpleExt {

	String echo(URL url,String s);

    @Adaptive({"key4"})
    void printA(URL url);

    @Adaptive
    void printB(URL url);

    @Adaptive({"key3","key2","key1"})
    void printC(URL url);
}

getAdaptiveExtension方法的大致流程如下图(从左到右) 在这里插入图片描述

接下来对每个方法进行解读。

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;
    }

createAdaptiveExtension

当前面的方法没有获取带自适应类实例的缓存时,会进入此方法。这个方法里一共做了三件事。

  1. getAdaptiveExtensionClass 先获取自适应类
  2. newInstance 再生成对应的实例
  3. injectExtension 然后为实例注入扩展类属性
private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

1getAdaptiveExtensionClass

这里将分为两步骤

  1. getExtensionClasses加载扩展点文件。若实现类上有@Adaptive注解,会被缓存起来,只能存在一个自适应类的缓存,默认是不可覆盖的,并且后续的执行方法都是用的此实现类。
  2. createAdaptiveExtensionClass创建自适应类(仅在自适应类缓存为空时进入此方法)
 private Class<?>  getAdaptiveExtensionClass() {
        //加载配置文件
        getExtensionClasses();
        //若自适应类有缓存则返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //自适应类的缓存为空则创建
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

1.1getExtensionClasses

此方法是dubbo SPI的核心方法,获取扩展类都需要经过这步(包括getDefalultExtension,getAdaptiveExtension,getActivateExtension)。同样还是先希望从缓存取,不存在就去加载文件填充缓存。

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;
    }
1.1.1loadExtensionClasses

这里也分为两步骤

  1. cacheDefaultExtensionName获取扩展点名称并缓存。例如@SPI("impl1")则是将"impl1"缓存下来
  2. loadDirectory解析扩展点文件并缓存实现类。例如配置文件内容为impl1=com.example.demo.impl.SimpleExtImpl1,则会以key="impl1" value= SimpleExtImpl1类的键值对 存在Map<String, Class<?>>中。除了进行普通扩展类的缓存,此方法还会额外做三种缓存(自适应类,包装扩展类,自动激活类)以加快后续方法的执行效率。
private Map<String, Class<?>> loadExtensionClasses() {
        //获取扩展点名称并缓存
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();

        for (LoadingStrategy strategy : strategies) {
            //加载指定文件夹下的配置文件
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            //同时加载alibaba和apache的类
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }

1.2 createAdaptiveExtensionClass

此方法将生成一个动态的自适应类,它能根据参数决定执行方法的实现类,主要做了三件事。

  1. 生成类代码字符串。生成代码的逻辑主要分为7步。

    1.1 生成与扩展点相同位置的packageimport ExtensionLoader这个类。为了不写其他的import方法,其他调用都使用全路径名。同时设置类名为“接口名称+$Adaptive”的格式。例如SimpleExt接口会生成SimpleExt&Adaptive

    1.2 遍历接口的所有方法,获取方法的返回类型,参数类型,异常类型等。

    1.3 生成校验参数是否为空的代码。

    1.4 生成默认实现类名称。如果@Adaptive注解没有默认值,则会先根据此名称去寻找它的扩展实现类。如SimpleExt的默认类名称为simple.ext

    1.5 生成获取扩展点名称的代码。例如对于前面的示例接口SimpleExtprintC方法,会生成url.getParameter("key3", url.getParameter("key2",url.getParameter("key1","impl1")));

    1.6 生成获取具体实现类的代码。通过上一步获得的extName执行getExtension(extName) 方法获取实现类。

    1.7 生成用此实现类调用方法的代码。

  2. 获取编译器的自适应类。实现Compile接口的AdaptiveCompiler类上有@Adaptive注解,因此此类会作为编译器的默认实现。

  3. 使用编译器编译类

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);
    }

2injectExtension

这一部分是为了给新创建的扩展点实现类注入属性。只有当此属性也为扩展点(且该扩展点已被加载并生成实例)时才能注入。 主要逻辑是遍历该类的所有方法,查看setter方法的返回类型对应的类是否为扩展点,是的话将其通过反射调用此setter方法注入。

private T injectExtension(T instance) {
        //此工厂中存储了已经实例化的扩展点,若工厂不为空,则查找工厂中是否拥有可注入的类
        if (objectFactory == null) {
            return instance;
        }

        try {
            //通过反射获取并遍历实现类的所有方法
            for (Method method : instance.getClass().getMethods()) {
                //只遍历以"set"命名开头的方法
                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];
                //若是基础类型则跳过,如 int,double,String等
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    //获取该属性名,如"setVersion" => "version"
                    String property = getSetterProperty(method);
                    //从扩展类工厂中获取该属性的实例
                    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;
    }

整体的流程就到此结束了,代码多处是反射和缓存的应用,如有错误欢迎指出~

如果有疑问,欢迎评论~ 如果成功解决了你的问题,点个赞再走吖~