天天看点

dubbo SPI @Adaptive源码解读概览getAdaptiveExtension——入口

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

目录

  • 概览
  • getAdaptiveExtension

    ——入口
    • createAdaptiveExtension

      • 1

        getAdaptiveExtensionClass

        • 1.1

          getExtensionClasses

          • 1.1.1

            loadExtensionClasses

        • 1.2

          createAdaptiveExtensionClass

      • 2

        injectExtension

概览

自适应类的用法示例

接口代码示例

@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

方法的大致流程如下图(从左到右)

dubbo SPI @Adaptive源码解读概览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);
        }
    }
           

1

getAdaptiveExtensionClass

这里将分为两步骤

  1. getExtensionClasses

    加载扩展点文件。若实现类上有

    @Adaptive

    注解,会被缓存起来,只能存在一个自适应类的缓存,默认是不可覆盖的,并且后续的执行方法都是用的此实现类。
  2. createAdaptiveExtensionClass

    创建自适应类(仅在自适应类缓存为空时进入此方法)
private Class<?>  getAdaptiveExtensionClass() {
        //加载配置文件
        getExtensionClasses();
        //若自适应类有缓存则返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //自适应类的缓存为空则创建
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
           

1.1

getExtensionClasses

此方法是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.1

loadExtensionClasses

这里也分为两步骤

  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 生成与扩展点相同位置的package。import

    ExtensionLoader

    这个类。为了不写其他的import方法,其他调用都使用全路径名。同时设置类名为“接口名称+$Adaptive”的格式。例如

    SimpleExt

    接口会生成

    SimpleExt&Adaptive

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

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

    1.4 生成默认实现类名称。如果

    @Adaptive

    注解没有默认值,则会先根据此名称去寻找它的扩展实现类。如

    SimpleExt

    的默认类名称为simple.ext。

    1.5 生成获取扩展点名称的代码。例如对于前面的示例接口SimpleExt的printC方法,会生成

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

2

injectExtension

这一部分是为了给新创建的扩展点实现类注入属性。只有当此属性也为扩展点(且该扩展点已被加载并生成实例)时才能注入。

主要逻辑是遍历该类的所有方法,查看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;
    }
           

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

如果有疑问,欢迎评论~

如果成功解决了你的问题,点个赞再走吖~

继续阅读