一、背景
我们团队的系统最近在进行商业化交付改造。为了能够达到快速、灵活、开放、稳定的目的,在架构层面进行了调整。其中在RPC通信层面,公司内部使用的是自研的JSF中间件。这次商业化改造决定采用DUBBO。
整体工程基于JDK17+Springboot3+DUBBO3
在商业化中,有些客户可能因为资源预算紧张,要求我们尽可能压缩资源。DUBBO本身是一款非常优秀的轻量级RPC开源框架,其3.x版本更稳定、更开放,很多组件都支持扩展,十分适合我们的诉求。我们可以采用直连模式从而减少注册中心的资源成本。
DUBBO支持丰富的配置方式,我们采用的是dubbo.properties配置文件的方式。配置示例如下:
properties
dubbo.reference.com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService.url=dubbo://${DUBBO_ADDRESS}/com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService
注意看,这里采用了${DUBBO_ADDRESS}
作为占位符,这样我们就可以通过环境变量实现配置化。假设我们是通过系统环境变量进行配置。在系统环境变量中添加:DUBBO_ADDRESS=127.0.0.1:20051
,在启动过程中会自动解析。
二、这是Spring EL表达式么?
乍一看,${DUBBO_ADDRESS}
和Spring EL表达式很像。就以为这是DUBBO在Spring生态下支持了Spring EL表达式。有一种理所当然的感觉。但本着有疑惑就要解决疑惑的原则,为了验证这个猜测,去官方文档寻找答案,结果发现官方没有这方面的资料。没办法,只能结合源码寻找答案了。
三、源码解析
Dubbo的初始化过程充分依赖Spring的启动过程。在Spring启动过程的每个阶段都做了很多事情。总体可分为三部分:
1)Reference的BeanDefinition初始化 2)DubboConfig初始化
3)DubboReferenceConfig填充
(一)ReferenceAnnotationBeanPostProcessor---Reference的BeanDefinition初始化
这个类是一个非常关键的类。分别继承了BeanFactoryPostProcessor
和ApplicationContextAware
等Spring的扩展类。做了很多关键的事前准备。其关键源码如下:
java
public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor
implements ApplicationContextAware, BeanFactoryPostProcessor {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
// 关键代码!!!通过主动触发getBean方法,实例化referenceBeanManager这个Bean,
// ReferenceBeanManager是用于管理Reference的。包含了对Reference的详细解析等。
this.referenceBeanManager =
applicationContext.getBean(ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);
this.beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
}
// 实现BeanFactoryPostProcessor的回调方法。用于识别含有@DubboReference注解的SpringBeanDefinition
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
Class<?> beanType;
// 是否是FactoryBean,不需要过多关注。
if (beanFactory.isFactoryBean(beanName)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (isReferenceBean(beanDefinition)) {
continue;
}
if (isAnnotatedReferenceBean(beanDefinition)) {
processReferenceAnnotatedBeanDefinition(beanName, (AnnotatedBeanDefinition) beanDefinition);
continue;
}
String beanClassName = beanDefinition.getBeanClassName();
beanType = ClassUtils.resolveClass(beanClassName, getClassLoader());
} else {
beanType = beanFactory.getType(beanName);
}
if (beanType != null) {
AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
try {
// 关键代码!!!在这个方法里面寻找当前的BeanDefination中源码是否含有@DubboReference/@Reference注解。
// 如果含有,则将@DubboReference或@Reference注解的成员属性注册为一个BeanDefinition。同时将Reference加入到referenceBeanManager中,用于后续流程的进一步处理。
// tips:referenceBeanManager是在上面通过getBean手动触发Bean的实例化产生的。
prepareInjection(metadata);
} catch (BeansException e) {
throw e;
} catch (Exception e) {
//...
}
}
}
if (beanFactory instanceof AbstractBeanFactory) {
List<BeanPostProcessor> beanPostProcessors = ((AbstractBeanFactory) beanFactory).getBeanPostProcessors();
for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
if (beanPostProcessor == this) {
beanDefinitionRegistry.removeBeanDefinition(BEAN_NAME);
break;
}
}
}
try {
// 关键代码!!!发布DubboConfigInitEvent事件。触发DubboConfigApplicationListener监听器
applicationContext.publishEvent(new DubboConfigInitEvent(applicationContext));
} catch (Exception e) {
// ...
}
}
}
通过继承BeanFactoryPostprocessor
成功识别出Reference
。这是关键的第一步 。
在
(二)DubboConfigApplicationListener
DubboConfigApplicationListener
监听DubboConfigInitEvent
事件。它主要干的事情就是实例化DubboConfigBeanInitializer
以及加载基础的Config。核心代码如下:
java
/**
* An ApplicationListener to load config beans. 用于加载ConfigBean
*/
public class DubboConfigApplicationListener
implements ApplicationListener<DubboConfigInitEvent>, ApplicationContextAware {
@Override
public void onApplicationEvent(DubboConfigInitEvent event) {
if (nullSafeEquals(applicationContext, event.getSource())) {
init(); // 委托
}
}
public void init() {
if (initialized.compareAndSet(false, true)) {
initDubboConfigBeans(); // 委托
}
}
private void initDubboConfigBeans() {
// 在这里if条件必然会命中,因为此时spring生命周期中已经开始了所有的Bean的实例化。
// 这个地方再通过spring加载bean的机制,通过getBean方法主动触发 DubboConfigBeanInitializer的实例化。
if (applicationContext.containsBean(DubboConfigBeanInitializer.BEAN_NAME)) {
applicationContext.getBean(DubboConfigBeanInitializer.BEAN_NAME, DubboConfigBeanInitializer.class);
} else {
// ...
}
// 在这里,调用Deployer的prepare()方法,先进行一部分初始化的动作。
// 后文中会重点分析Deployer类。
moduleModel.getDeployer().prepare();
}
}
在上面源码中分析到:方法中通过主动调用applicationContext.getBean
方法,触发DubboConfigBeanInitializer
的实例化;
而DubboConfigBeanInitializer
实际上是一个FactoryBean
,也干了很关键的事情。
Tips:Spring生命周期中,Bean的实例化和Bean的初始化完全是两个不同的阶段。实例化在先,初始化在后。
(三)DubboConfigBeanInitializer
它继承了InitializingBean
。InitializingBean
是Spring提供的一个扩展类。可以在Bean初始化完成之后回调afterPropertiesSet()
方法。目的是实例化Reference对应的ReferenceConfig为后续解析ReferenceConfig做准备。源码如下:
java
public class DubboConfigBeanInitializer implements BeanFactoryAware, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
init(); // 委托
}
private void init() {
if (initialized.compareAndSet(false, true)) {
// 从Spring容器中获��referenceBeanManager
referenceBeanManager = beanFactory.getBean(ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);
try {
// 只是用于设置Dubbo支持的基础的Config类型
prepareDubboConfigBeans();
//关键代码!!!触发referenceBean的解析
referenceBeanManager.prepareReferenceBeans();
} catch (Throwable e) {
// ...
}
}
}
}
(四)referenceBeanManager.prepareReferenceBeans()
在上文中最为关键的是referenceBeanManager.prepareReferenceBeans();
。这个方法主要作用就是初始化referenceBeans.源码如下:
java
public void prepareReferenceBeans() throws Exception {
initialized = true;
// getReferences()方法获取referenceBean集合。在ReferenceAnnotationBeanPostProcessor中初始化ReferenceBean并添加到this.referenceBeanMap容器中的。
for (ReferenceBean referenceBean : getReferences()) {
initReferenceBean(referenceBean); // 委托
}
}
public Collection<ReferenceBean> getReferences() {
return new HashSet<>(referenceBeanMap.values());
}
public synchronized void initReferenceBean(ReferenceBean referenceBean) throws Exception {
if (referenceBean.getReferenceConfig() != null) {
return;
}
// reference key
String referenceKey = getReferenceKeyByBeanName(referenceBean.getId());
if (StringUtils.isEmpty(referenceKey)) {
referenceKey = ReferenceBeanSupport.generateReferenceKey(referenceBean, applicationContext);
}
ReferenceConfig referenceConfig = referenceConfigMap.get(referenceKey);
if (referenceConfig == null) {
// 关键代码!!!实例化referenceBean对应的ReferenceConfig。
// dubbo中ReferenceBean的属性都是通过ReferenceConfig进行配置的。
// 在这里,先实例化处ReferenceConfig,后续会进一步解析并完善ReferenceConfig里面的属性值。
Map<String, Object> referenceAttributes = ReferenceBeanSupport.getReferenceAttributes(referenceBean);
referenceConfig = ReferenceCreator.create(referenceAttributes, applicationContext)
.defaultInterfaceClass(referenceBean.getObjectType())
.build();
// set id if it is not a generated name
if (referenceBean.getId() != null && !referenceBean.getId().contains("#")) {
referenceConfig.setId(referenceBean.getId());
}
// cache referenceConfig
referenceConfigMap.put(referenceKey, referenceConfig);
// register ReferenceConfig
moduleModel.getConfigManager().addReference(referenceConfig);
moduleModel.getDeployer().setPending();
}
// 将 referenceConfig 绑定到对应的 referenceBean
referenceBean.setKeyAndReferenceConfig(referenceKey, referenceConfig);
}
到这里,完成了ReferenceBean对应的Config的实例化。Reference和ReferenceConfig也有了映射关系。
(五)DubboDeployApplicationListener
这又是一个监听器。它监听的是ApplicationContextEvent
。Spring在完成所有Bean的初始化以及相关扩展接口执行完成之后,会发布ContextRefreshedEvent
,它继承了ApplicationContextEvent
。源码地址在:AbstraceApplicationContext#finishRefresh()
方法中。
Dubbo在这个监听器中完成reference最终的加载步骤,其中就包括填充属性。核心源码如下:
java
public class DubboDeployApplicationListener
implements ApplicationListener<ApplicationContextEvent>, ApplicationContextAware, Ordered {
```
@Override
public int getOrder() { // 所有监听器中,执行优先级最低,确保SpringBean的正确性。
return LOWEST_PRECEDENCE;
}
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (nullSafeEquals(applicationContext, event.getSource())) {
if (event instanceof ContextRefreshedEvent) {
// 关键代码!!!委托
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
}
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
ModuleDeployer deployer = moduleModel.getDeployer();
Assert.notNull(deployer, "Module deployer is null");
Object singletonMutex = LockUtils.getSingletonMutex(applicationContext);
// start module
Future future = null;
synchronized (singletonMutex) {
// 关键代码!!!核心入口。
future = deployer.start();
}
// if the module does not start in background, await finish
if (!deployer.isBackground()) {
try {
future.get();
} catch (InterruptedException e) {
// ...
} catch (Exception e) {
// ...
}
}
}
通过源码分析,最终是委托给deployer
完成最终的装配。
(六)DefaultModuleDeployer
在Dubbo中,DefaultModuleDeployer
是一个非常核心的组件。可以完成加载配置、初始化组件、注册服务等功能。start()
方法是入口。
java
public class DefaultModuleDeployer extends AbstractDeployer<ModuleModel> implements ModuleDeployer {
@Override
public Future start() throws IllegalStateException {
// 调用初始化方法。
applicationDeployer.initialize();
// 调用startSync方法。
return startSync();
}
@Override
public void initialize() {
// 在DubboApplicationListener中,执行了preper方法,就已经进行了初始化。
// 所以在DubboDeployApplicationListener中执行初始化方法时,不会再执行下面的代码逻辑了
if (initialized) {
return;
}
// Ensure that the initialization is completed when concurrent calls
synchronized (startLock) {//双重校验锁保证原子操作。
if (initialized) {
return;
}
onInitialize();
// register shutdown hook
registerShutdownHook();
startConfigCenter();
// 重要。在这里面完成核心Dubbo的config的加载。比如dubbo.application/dubbo.registries等。但不包括dubbo.reference
loadApplicationConfigs();
initModuleDeployers();
initMetricsReporter();
initMetricsService();
// @since 2.7.8
startMetadataCenter();
// 变更初始化状态
initialized = true;
}
}
private synchronized Future startSync() throws IllegalStateException {
if (isStopping() || isStopped() || isFailed()) {
throw new IllegalStateException(getIdentifier() + " is stopping or stopped, can not start again");
}
try {
if (isStarting() || isStarted()) {
return startFuture;
}
onModuleStarting();
// 实际上不会再执行初始化方法了,原因在上面说了
initialize();
// export services
exportServices();
// prepare application instance
// exclude internal module to avoid wait itself
if (moduleModel != moduleModel.getApplicationModel().getInternalModule()) {
applicationDeployer.prepareInternalModule();
}
// 关键代码!!! 对服务进行配置处理。
referServices();
// 省略...
return startFuture;
}
private void referServices() {
// 从ReferenceConfigManager中获取referenceConfigs并进行刷新。
configManager.getReferences().forEach(rc -> {
try {
ReferenceConfig<?> referenceConfig = (ReferenceConfig<?>) rc;
if (!referenceConfig.isRefreshed()) {
// 关键代码!!!调用刷新方法。
referenceConfig.refresh();
}
// ...
} catch (Throwable t) {
// ...
referenceCache.destroy(rc);
throw t;
}
});
}
}
通过上面的源码解析,最终来到了配置解析的最后一个关键源码处。
(七)AbstruseConfig#refresh
在这个方法里面,进行了最终的解析。源码如下:
java
public abstract class AbstractConfig implements Serializable {
public void refresh() {
if (needRefresh) {
try {
// 关键代码!!!在刷新之前做一些事情。
// reference场景下,其子类实现方法是:ReferenceConfigBase#preProcessRefresh。
// 在子类里面也调用了refreshWithPrefixes(getPrefixes(), getConfigMode());方法
// 不同的是,在ReferenceConfigBase#preProcessRefresh中,getPrefixes()获取到的是dubbo.reference的配置前缀。
preProcessRefresh();
// 而在当前方法中,getPrefixes()获取到的前缀是:
// dubbo.reference.com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService
// 这正是我们在dubbo.properties中的配置。
refreshWithPrefixes(getPrefixes(), getConfigMode());
} catch (Exception e) {
// ...
}
postProcessRefresh();
}
refreshed.set(true);
}
/**
*根据配置前缀刷新
*/
protected void refreshWithPrefixes(List<String> prefixes, ConfigMode configMode) {
// 获取环境对象。
// 这里的环境对象是Dubbo的自己封装的环境对象,持有系统环境变量/JVM环境变量以及Dubbo自身的环境变量配置。
// 这就是为什么Dubbo支持丰富的配置方式的原因。
Environment environment = getScopeModel().modelEnvironment();
// 获取所有的环境变量。
// key->环境变量类型。比如:系统环境变量/JVM环境变量等。
// value-> 对应的变量配置。
List<Map<String, String>> configurationMaps = environment.getConfigurationMaps();
// 确认最终的前缀。
// 以Reference场景为例,如果是this.refresh()方法中调用的话,那么这里的Prefix就是dubbo.properties中的配置前缀
String preferredPrefix = null;
for (String prefix : prefixes) {
if (ConfigurationUtils.hasSubProperties(configurationMaps, prefix)) {
preferredPrefix = prefix;
break;
}
}
if (preferredPrefix == null) {
preferredPrefix = prefixes.get(0);
}
// Extract sub props (which key was starts with preferredPrefix)
Collection<Map<String, String>> instanceConfigMaps = environment.getConfigurationMaps(this, preferredPrefix);
// 关键代码!!!获取指定前缀的属性配置。
// exp:
// config->dubbo.reference.com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService.url
// prefix->dubbo.reference.com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService
// subProperty->url
// 这样我们就可以解析url的配置了
Map<String, String> subProperties = ConfigurationUtils.getSubProperties(instanceConfigMaps, preferredPrefix);
InmemoryConfiguration subPropsConfiguration = new InmemoryConfiguration(subProperties);
// 关键代码!!!填充配置
// 我们的配置是dubbo://${DUBBO_ADDRESS}/com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService
// 通过填充配置对${DUBBO_ADDRESS}占位符进行替换。
assignProperties(this, environment, subProperties, subPropsConfiguration, configMode);
// process extra refresh of subclass, e.g. refresh method configs
processExtraRefresh(preferredPrefix, subPropsConfiguration);
}
private void assignProperties(
Object obj,
Environment environment,
Map<String, String> properties,
InmemoryConfiguration configuration,
ConfigMode configMode) {
// if old one (this) contains non-null value, do not override
// 覆盖模式。即根据配置优先级确认最终有效的配置。本示例中只有dubbo.properties配置
boolean overrideIfAbsent = configMode == ConfigMode.OVERRIDE_IF_ABSENT;
// even if old one (this) contains non-null value, do override
boolean overrideAll = configMode == ConfigMode.OVERRIDE_ALL;
// loop methods, get override value and set the new value back to method
// 关键代码!!!通过反射机制获取到ReferenceConfig所有的method方法,其中就包括setUrl
List<Method> methods =
MethodUtils.getMethods(obj.getClass(), method -> method.getDeclaringClass() != Object.class);
for (Method method : methods) {
// 关键代码!!!判断是否是setXXX
if (MethodUtils.isSetter(method)) {
String propertyName = extractPropertyName(method.getName());
// if config mode is OVERRIDE_IF_ABSENT and property has set, skip
if (overrideIfAbsent && isPropertySet(methods, propertyName)) {
continue;
}
// 关键代码!!!格式化属性名称。
// 本示例中,这里获取到的属性名称为url
String kebabPropertyName = StringUtils.convertToSplitName(propertyName, "-");
try {
// 关键代码!!!获取属性对应的配置。
// 本示例中,这里的value为:dubbo://${DUBBO_ADDRESS}/com.jd.xxx.xxx.lite.api.org.OrgStaffInternalRpcService
String value = StringUtils.trim(configuration.getString(kebabPropertyName));
if (StringUtils.hasText(value)
&& ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)
&& !isIgnoredAttribute(obj.getClass(), propertyName)) {
// 关键代码!!!庐山真面目在此!
// 在这里解析占位符。
// 通过上文介绍道,这里的environment是Dubbo自己封装的。
// 也就意味着,解析也是Dubbo框架自己解析的。因为Dubbo支持丰富的参数配置,包括Dubbo自身提供的方式。所以必然不可能是Spring EL表达式的解析方式。
value = environment.resolvePlaceholders(value);
if (StringUtils.hasText(value)) {
Object arg = ClassUtils.convertPrimitive(
ScopeModelUtil.getFrameworkModel(getScopeModel()),
method.getParameterTypes()[0],
value);
if (arg != null) {
// 反射调用。
// 本示例中就是,反射调用setUrl()方法。
method.invoke(obj, arg);
}
}
}
} catch (Exception e) {
// ...
} else if (isParametersSetter(method)) { // 非setter方法的解析方式,原理一样的。
// ...
} else if (isNestedSetter(obj, method)) {
// ...
}
}
}
}
总结
通过以上源码分析,知道了dubbo.properties中占位符的解析原理。
此外,对于Dubbo作为一个开源框架的灵活性有了更深的感悟。
单纯从技术角度出发,Dubbo对于Spring生命周期的运用十分值得学习和借鉴。