背景
接着前面的分析 org.apache.dubbo.common.extension.ExtensionLoader#injectExtension(instance)的功能。我们再次回忆下 前面我们已经分析了spi 是怎么创建实现类的流程,其实到这里spi的功能按理说就结束了,但是dubbo作为一款流行的框架,要提高适用性肯定不会仅限于此,injectExtension 主要扩展了spring 的依赖注入,以及支持 spi 的包装器。
源码分析
1.1
//org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
private T injectExtension(T instance) {
if (objectFactory != null) {
//遍历实现类中的方法,如果是set方法则继续执行
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
//解析类似setUsername 将set和驼峰去了返回 username
String property = getSetterProperty(method);
//异步源码分析 1.2
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//通过反射调用set方法进行赋值
method.invoke(instance, object);
}
}
}
}
return instance;
}
1.2
//Object object = objectFactory.getExtension(pt, property);
//org.apache.dubbo.common.extension.ExtensionFactory#getExtension
@SPI
public interface ExtensionFactory {
<T> T getExtension(Class<T> type, String name);
很明显这是一个扩展点,在执行objectFactory.getExtension() 的时候我们需要找到接口的实现类才能继续往下debug,我们瞄一眼 objectFactory 属性的定义
private final ExtensionFactory objectFactory;
private ExtensionLoader(Class<?> type) {
//type是我们传入的Job 接口 所以赋值为 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
在ExtensionLoader初始化的时候会给该属性赋值为 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(),上篇文章我们分析过,通过方法 getAdaptiveExtension 将返回该扩展点的自适应扩展点实现,我们先看看接口实现
我们看到其中一个类 AdaptiveExtensionFactory 带有类注解 @Adaptive 自然走到了类扩展点实现,而不是方法扩展点,那么 getAdaptiveExtension() 方法返回的就是 AdaptiveExtensionFactory 对象,继续debug
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 把 ExtensionFactory 的所有实现类都加载出来了包括
//SpiExtensionFactory 和 SpringExtensionFactory
factories = Collections.unmodifiableList(list);
}
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
上面代码第12行,遍历factories中的2个扩展点,调用扩展点的 getExtension(type, name) 方法,我们举个例子,在前面demo中ApolloConfigCenter 实现类中加入一个User对象
那么解析完,对应的type,name 分别为User<class>,hupuUser<String>,我们先进到 SpringExtensionFactory 的getExtension实现中去
1.2.1 SpringExtensionFactory
public <T> T getExtension(Class<T> type, String name) {
//属性类型跳过SPI注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
//遍历spirng的ApplicationContext容器,具体解析到1.2.1.1
for (ApplicationContext context : CONTEXTS) {
if (context.containsBean(name)) {
//使用过spring的应该知道很熟悉的方法,调用 getBean 得到bean
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
if (Object.class == type) {
return null;
}
//这边一样的道理,如果通过name获取不到,尝试通过Class 的方式获取
for (ApplicationContext context : CONTEXTS) {
try {
return context.getBean(type);
} catch (NoUniqueBeanDefinitionException multiBeanExe) {
logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
} catch (NoSuchBeanDefinitionException noBeanExe) {
if (logger.isDebugEnabled()) {
logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
}
}
}
return null;
}
1.2.1.1
private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
//org...bbo.config.spring.extension.SpringExtensionFactory#addApplicationContext
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);//将context收集到set中
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
DubboShutdownHook.getDubboShutdownHook().unregister();
}
BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
}
//org.apache.dubbo.config.spring.ServiceBean#setApplicationContext
public void setApplicationContext(ApplicationContext applicationContext) {
//ServiceBean 是dubbo的类,实现了 ApplicationContextAware
//这样在dubbo开始初始化的时候,就会被调用到将ApplicationContext 对象赋值给了
//SpringExtensionFactory的CONTEXTS,并且还支持多个/多层容器
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
supportedApplicationListener = addApplicationListener(applicationContext, this);
}
1.2.2 SpiExtensionFactory
看完 SpringExtensionFactory 的实现,我们看看SpiExtensionFactory的实现,这个比较简单
public class SpiExtensionFactory implements ExtensionFactory {
@Override
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;
}
}
还是ApolloConfigCenter ,SpiExtensionFactory 的含义就说如果ApolloConfigCenter类中有扩展点的属性,则会将扩展点的自适应扩展点赋值
如上,login 是一个扩展点,有weixin,phone等实现,如果我们在 ApolloConfigCenter 这样定义之后,spi 将会给login变量附上值。
流程deug
为了验证上面分析,我们运行debug,这次不只是单独测试,而是会结合spring容器。
//dubbo-provider.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<bean id="myUser" class="com.poizon.study.provider.User"/>
</beans>
//com.poizon.study.provider.spi.NacosConfigCenter
public class NacosConfigCenter implements ConfigCenter {
private User myUser;//并没有带任何注解
public User getMyUser() {return myUser;}
public void setMyUser(User myUser) { this.myUser = myUser;}
private Protocol protocol; //并没有带任何注解
public void setProtocol(Protocol protocol) { this.protocol = protocol;}
@Override
public String get(String key) {
System.out.println(protocol);
return "nacos " + myUser.getName();
}
}
//测试demo
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dubbo-provider.xml");
ctx.start();
ExtensionLoader<ConfigCenter> extensionLoader = ExtensionLoader.getExtensionLoader(ConfigCenter.class);
ConfigCenter weixin = extensionLoader.getExtension("nacos");
String key = weixin.get("key");
System.out.println(key);
//打印
//org.apache.dubbo.rpc.Protocol$Adaptive@5a9d6f02
//nacos 忠犬八公
System.in.read();
}
很清晰,NacosConfigCenter的myUser 属性已经赋值为spring中的user bean,protocol 属性已经赋值为Protocol接口的自适应扩展点。
总结
我们接着扩展,如果实现类和属性是继承同一个接口,那么就可以理解为包装器,类似这样用法dubbo里面有很多,比如Cluster中的MockClusterWrapper,Protocol中的ProtocolFilterWrapper 等等,这些我们会在后面介绍到,关于spi就介绍到这。