一个 dubbo 和 springboot 的兼容性问题( 二 )


?其中 , 我们使用 Springboot 版本是2.2.0 。
于是手动跟踪调试了下,发现某些后置处理器执行时,在解决它的依赖时,会去创建 dubbo 服务的 bean,然后就会创建 ApplicationConfig 对象了 。注意这个对象其实只是new了一下,所有属性都是默认值 , 并非来源于我们定义的 dubbo 配置文件 。
而我们定义的 dubbo 配置文件 , 是在 ApplicationConfig 中的 addIntoConfigManager 中注入的:
public abstract class AbstractConfig implements Serializable {...@PostConstructpublic void addIntoConfigManager() {ApplicationModel.getConfigManager().addConfig(this);}...}
其中,@PostConstruct 注解会由 CommonAnnotationBeanPostProcessor 这个后置处理器(继承自 InitDestroyAnnotationBeanPostProcessor)解析执行 。但很遗憾,此时它还没有被执行,因此也就没有把正确的dubbo配置注入进去,最终导致dubbo框架在校验时发现无效配置 , 继而报错 。
网上相关讨论及解决方案关于这类报错 , 网上也有不少讨论,如:

  • No application config found or it's not a valid config!
  • No registry config found or it's not a valid config!
  • 同样的配置,2.7.3启动成功,2.7.6启动报错
简单总结起来,由于dubbo没有一个固定的初始化时机 , 而是与 ReferenceBean 等 dubbo 框架中的 beans 初始化相关 。 如果它们被过早初始化,导致某些 BeanPostProcessor 尚未被执行,就会出现dubbo配置丢失的问题 。
自然,dubbo官方也注意到了这个问题,于是在3.x版本进行了改造,针对该问题做了优化 , 参见Dubbo 3 Spring相关优化 。
但3.x做了大量重构,如果我们不想升级,应该怎么办呢?其实根据上面提到的原因,我们可以自己定义一个后置处理器,拦截 dubbo 框架的 beans,并手动注入对应的配置 。本质上来说,将之前来不及执行的注入代码提到前面去 。这样,我们前面提到的「某些后置处理器执行时 , 在解决它的依赖时,会去创建 dubbo 服务的 bean,然后就会创建 ApplicationConfig 对象了」 , 就变成了「某些后置处理器执行时,在解决它的依赖时,会去创建 dubbo 服务的 bean , 此时会被我们自定义的后置处理器拦截,并注入对应的dubbo配置,然后就会创建正确的 ApplicationConfig 对象」 。
具体做法为:
【一个 dubbo 和 springboot 的兼容性问题】step1. 定义一个后置处理器,识别到 bean 属于 AbstractConfig 配置后,将其注入:
package com.xxx;import org.apache.dubbo.config.AbstractConfig;import org.apache.dubbo.rpc.model.ApplicationModel;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;public class DubboBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof AbstractConfig) {AbstractConfig abstractConfig = (AbstractConfig) bean;ApplicationModel.getConfigManager().addConfig(abstractConfig);}return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}}
step2. 定义一个 Initializer,并将刚才定义的 DubboBeanPostProcessor 注入:
package com.xxx;import org.springframework.context.ApplicationContextInitializer;import org.springframework.context.ConfigurableApplicationContext;public class DubboApplicationContextInitializerimplements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.getBeanFactory().addBeanPostProcessor(new DubboBeanPostProcessor());}}
step3. 把 DubboApplicationContextInitializer 注入到Spring框架中,可以采用多种方式 。
方式1. 直接在启动类注入:
@ImportResource(locations = {"classpath:dubbo.xml"})@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})public class Launcher extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(Launcher.class);springApplication.addInitializers(new DubboApplicationContextInitializer());springApplication.run(args);}}方式2. 在 resources 的 META-INF 目录下配置 spring.factories 文件并写入:
org.springframework.context.ApplicationContextInitializer=com.xxx.DubboApplicationContextInitializer

推荐阅读