Dubbo源码|六、Dubbo SPI 依赖注入
开篇
本文主要介绍Dubbo SPI 加载扩展点后,属性的依赖注入以及Adaptive注解相关知识。
引子
假如有这样一个需求,我们再定义一个扩展点,这个扩展点内有个属性是另外一个扩展点,那么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并没有给我赋值,而且还报了一个错,那如何才能正确的赋值呢?

我们来分析一下,首先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属性值。

不过可能有些奇怪,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这个方法实现的。

- 首先先遍历类里面的所有方法。
- 判断是否是
setter方法,从这里可以看出是根据set方法进行赋值的。 - 判断方法上是否有
DisableInject注解,可以通过这个注解来忽略赋值。 - 判断是否是
setter方法的参数类型,如果是基础类型也会直接忽略。 - 获取属性名称,也就是set方法去掉set再把第一个字符小写。
- 从对象工厂
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中,即把SpiExtensionFactory和SpringExtensionFactory对象放到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类中,方法比较多,但也比较简单,就是按模板生成对应的代码。

生成类的样子,前面已经说过了,这里就不再赘述了。
后记
DubboSPI的依赖注入,到这里就基本上介绍完了。
文中所说的被@Adaptive标注的方法的参数一定是URL吗?这个是不一定的,也可以是一个普通类,但普通类里面必须有URL类型的属性,比如Invoker这个类。
转载自:https://juejin.cn/post/7172821062929416205