likes
comments
collection
share

Dubbo源码|六、Dubbo SPI 依赖注入

作者站长头像
站长
· 阅读数 48

开篇

本文主要介绍Dubbo SPI 加载扩展点后,属性的依赖注入以及Adaptive注解相关知识。

Dubbo SPI扩展机制(一)

Dubbo SPI扩展机制(二)

引子

假如有这样一个需求,我们再定义一个扩展点,这个扩展点内有个属性是另外一个扩展点,那么Dubbo会给这个属性赋值吗?写个例子试下:

创建一个掘友接口

@SPI
public interface JueYou {
    Mascot getMascot();
}
public class JavaCub implements JueYou {
    private Mascot mascot;
    public void setMascot(Mascot mascot) {
        this.mascot = mascot;
    }
    @Override
    public Mascot getMascot() {
        return mascot;
    }
}

在resources/META-INF/dubbo目录下创建文件cn.juejin.spi.JueYou

javacub=cn.juejin.spi.JavaCub

运行一下,可以看到dubbo并没有给我赋值,而且还报了一个错,那如何才能正确的赋值呢?

Dubbo源码|六、Dubbo SPI 依赖注入

我们来分析一下,首先JavaCub这个类的mascot属性是接口类型并且有多个实现类,如果要进行赋值的话,肯定需要指定一下赋值哪一个对象是不是?那如果指定呢?

要指定默认扩展点吗?经过尝试是不行的。

@SPI("yoyo")
public interface Mascot {
    String getName();
}

那该怎么办呢?其实,要实现这个功能,需要加一个URL参数,虽然感觉很怪,但是这是Dubbo的机制,Dubbo就是通过这种方式来实现的。

Mascot接口以及实现类进行改造,增加URL参数。

@SPI
public interface Mascot {
    @Adaptive
    String getName(URL url);
}
public class Click implements Mascot {
    @Override
    public String getName(URL url) {
        return "Click";
    }
}
public class YoYo implements Mascot {
    @Override
    public String getName(URL url) {
        return "YoYo";
    }
}

再改造下运行方法,查看结果,现在就可以获取到javacub这个对象的mascot属性值了,并且还可以拿到name属性值。

Dubbo源码|六、Dubbo SPI 依赖注入

不过可能有些奇怪,javacub.getMascot()的值为什么是cn.juejin.spi.Mascot$Adaptive@67b64c45?这是一个Dubbo生成的代理类,来看下这个类长什么样子。

public class Mascot$Adaptive implements cn.juejin.spi.Mascot {
    public java.lang.String getName(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("mascot");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (cn.juejin.spi.Mascot) name from url (" + url.toString() + ") use keys([mascot])");
        cn.juejin.spi.Mascot extension = (cn.juejin.spi.Mascot) ExtensionLoader.getExtensionLoader(cn.juejin.spi.Mascot.class).getExtension(extName);
        return extension.getName(arg0);
    }
}

可以看到,在这个类中是根据url的参数获取指定的注入对象的。String extName = url.getParameter("mascot");

源码分析

Dubbo是如何实现依赖注入的呢?Dubbo给扩展点的实例属性赋值是在org.apache.dubbo.common.extension.ExtensionLoader#injectExtension这个方法实现的。

Dubbo源码|六、Dubbo SPI 依赖注入

  1. 首先先遍历类里面的所有方法。
  2. 判断是否是setter方法,从这里可以看出是根据set方法进行赋值的。
  3. 判断方法上是否有DisableInject注解,可以通过这个注解来忽略赋值。
  4. 判断是否是setter方法的参数类型,如果是基础类型也会直接忽略。
  5. 获取属性名称,也就是set方法去掉set再把第一个字符小写。
  6. 从对象工厂objectFactory里获取对应的值,赋值给对应的属性。

objectFactory

以上几步就是Dubbo对扩展点属性复制的过程,Dubbo支持从多种容器里获取对象进行赋值,即objectFactory可以获取Dubbo生成的代理对象也可以从Spring容器中获取对象给属性复制。因此,objectFactory对应的类,也有SPI注解。

@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

objectFactory的初始化是在ExtensionLoader的构造方法中。

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory =
            (type == ExtensionFactory.class ? null : 
            ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

ExtensionLoader.getExtensionLoader(ExtensionFactory.class)这个我们已经熟悉了,是获取ExtensionFactory的扩展点加载器,那后面这个方法getAdaptiveExtension什么作用呢?

AdaptiveExtensionFactory

这个方法的作用是,获取接口实现类中含有@Adaptive注解的实现类。对于ExtensionFactory接口来说,获取的是AdaptiveExtensionFactory类实力。 AdaptiveExtensionFactory类在初始化的时候,又会寻找ExtensionFactory的所有扩展点,把支持的扩展点类对象放到list中,即把SpiExtensionFactorySpringExtensionFactory对象放到factories中。

public AdaptiveExtensionFactory() {
    ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
    List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
    for (String name : loader.getSupportedExtensions()) {
        list.add(loader.getExtension(name));
    }
    factories = Collections.unmodifiableList(list);
}

SpringExtensionFactory

看Dubbo是如何从Spring容器中获取对象的呢?

public <T> T getExtension(Class<T> type, String name) {
    if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
        return null;
    }
    for (ApplicationContext context : CONTEXTS) {
        T bean = getOptionalBean(context, name, type);
        if (bean != null) {
            return bean;
        }
    }
    return null;
}
public static <T> T getOptionalBean(ListableBeanFactory beanFactory, String beanName, Class<T> beanType) throws BeansException {
    if (beanName == null) {
        return getOptionalBeanByType(beanFactory, beanType);
    }
    T bean = null;
    try {
        bean = beanFactory.getBean(beanName, beanType);
    } catch (NoSuchBeanDefinitionException e) {
    } catch (BeanNotOfRequiredTypeException e) {
        logger.warn(String.format("bean type not match, name: %s, expected type: %s, actual type: %s",
                beanName, beanType.getName(), e.getActualType().getName()));
    }
    return bean;
}

可以看到,先按接口类型ByType方式从beanFactory中查找,找不到的话再按ByName方式从beanFactory中查找,再找不到则返回null。

SpiExtensionFactory

public <T> T getExtension(Class<T> type, String name) {
    if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
        ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
        if (!loader.getSupportedExtensions().isEmpty()) {
            return loader.getAdaptiveExtension();
        }
    }
    return null;
}

如果从spring的bean工厂里没有找到对象的话,就从spi工厂里查找对象,根据接口类型获取对应的扩展点加载器,再获取对应的Adaptive代理对象。也就是从接口的实现类中寻找有没有被Adaptive注解标注的类,如果没有的话,会自动生成一个Adaptive类,比如前面说的Mascot$Adaptive

生成Adaptive代理类

生成代理类的代码在org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass方法中,

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

也就是先生成代理类的代码,再通过编译生成对应的代理类。

生成代理类的代码在org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator类中,方法比较多,但也比较简单,就是按模板生成对应的代码。

Dubbo源码|六、Dubbo SPI 依赖注入

生成类的样子,前面已经说过了,这里就不再赘述了。

后记

DubboSPI的依赖注入,到这里就基本上介绍完了。

文中所说的被@Adaptive标注的方法的参数一定是URL吗?这个是不一定的,也可以是一个普通类,但普通类里面必须有URL类型的属性,比如Invoker这个类。

转载自:https://juejin.cn/post/7172821062929416205
评论
请登录