6. SPI依赖注入
1. 开篇
本文主要介绍Dubbo SPI 加载扩展点后,属性的依赖注入以及Adaptive
注解相关知识。
4.Dubbo 扩展机制—基础应用
- Dubbo 扩展机制—加载扩展点
2. 引子
假如有这样一个需求,我们再定义一个扩展点,这个扩展点内有个属性是另外一个扩展点,那么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");
3. 源码分析
Dubbo是如何实现依赖注入的呢?Dubbo给扩展点的实例属性赋值是在org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
这个方法实现的。
- 首先先遍历类里面的所有方法。
- 判断是否是
setter
方法,从这里可以看出是根据set
方法进行赋值的。 - 判断方法上是否有
DisableInject
注解,可以通过这个注解来忽略赋值。 - 判断是否是
setter
方法的参数类型,如果是基础类型也会直接忽略。 - 获取属性名称,也就是set方法去掉set再把第一个字符小写。
- 从对象工厂
objectFactory
里获取对应的值,赋值给对应的属性。
3.1 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
什么作用呢?
3.2 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);
}
3.3 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。
3.4 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
。
3.5 生成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
类中,方法比较多,但也比较简单,就是按模板生成对应的代码。
生成类的样子,前面已经说过了,这里就不再赘述了。
4. 后记
DubboSPI的依赖注入,到这里就基本上介绍完了。
文中所说的被@Adaptive
标注的方法的参数一定是URL
吗?这个是不一定的,也可以是一个普通类,但普通类里面必须有URL
类型的属性,比如Invoker
这个类。
作者:Java星辰 链接:https://juejin.cn/post/7172821062929416205