一个 dubbo 和 springboot 的兼容性问题
背景介绍
最近把dubbo的版本从2.7.3升级到2.7.15时,遇到一个报错 No application config found or it's not a valid config! ,对应的异常栈为:
Caused by: java.lang.IllegalStateException: No application config found or it's not a valid config! Please add <dubbo:application name="..." /> to your spring config. at org.apache.dubbo.config.utils.ConfigValidationUtils.validateApplicationConfig(ConfigValidationUtils.java:419) ~[dubbo-2.7.15.jar:2.7.15] at org.apache.dubbo.config.bootstrap.DubboBootstrap.checkGlobalConfigs(DubboBootstrap.java:539) ~[dubbo-2.7.15.jar:2.7.15] at org.apache.dubbo.config.bootstrap.DubboBootstrap.initialize(DubboBootstrap.java:525) ~[dubbo-2.7.15.jar:2.7.15] at org.apache.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:244) ~[dubbo-2.7.15.jar:2.7.15] at org.apache.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:206) ~[dubbo-2.7.15.jar:2.7.15] at org.apache.dubbo.config.spring.ReferenceBean.getObject(ReferenceBean.java:68) ~[dubbo-2.7.15.jar:2.7.15] at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:171) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:101) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1818) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1266) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:260) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeanByName(AbstractAutowireCapableBeanFactory.java:454) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:543) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:513) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:653) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:224) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:334) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1429) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:400) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1287) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:874) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:778) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:528) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.aop.framework.autoproxy.BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans(BeanFactoryAdvisorRetrievalHelper.java:91) ~[spring-aop-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findCandidateAdvisors(AbstractAdvisorAutoProxyCreator.java:109) ~[spring-aop-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors(AnnotationAwareAspectJAutoProxyCreator.java:92) ~[spring-aop-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.shouldSkip(AspectJAwareAdvisorAutoProxyCreator.java:101) ~[spring-aop-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessBeforeInstantiation(AbstractAutoProxyCreator.java:251) ~[spring-aop-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1141) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1114) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:506) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE] ... 12 more
其中,我们使用 Springboot 版本是2.2.0。
于是手动跟踪调试了下,发现某些后置处理器执行时,在解决它的依赖时,会去创建 dubbo 服务的 bean,然后就会创建 ApplicationConfig 对象了。注意这个对象其实只是new了一下,所有属性都是默认值,并非来源于我们定义的 dubbo 配置文件。
而我们定义的 dubbo 配置文件,是在 ApplicationConfig 中的 addIntoConfigManager 中注入的:
public abstract class AbstractConfig implements Serializable { ... @PostConstruct public void addIntoConfigManager() { ApplicationModel.getConfigManager().addConfig(this); } ... }
其中,@PostConstruct 注解会由 CommonAnnotationBeanPostProcessor 这个后置处理器(继承自 InitDestroyAnnotationBeanPostProcessor)解析执行。但很遗憾,此时它还没有被执行,因此也就没有把正确的dubbo配置注入进去,最终导致dubbo框架在校验时发现无效配置,继而报错。
网上相关讨论及解决方案
关于这类报错,网上也有不少讨论,如:
简单总结起来,由于dubbo没有一个固定的初始化时机,而是与 ReferenceBean 等 dubbo 框架中的 beans 初始化相关。 如果它们被过早初始化,导致某些 BeanPostProcessor 尚未被执行,就会出现dubbo配置丢失的问题。
自然,dubbo官方也注意到了这个问题,于是在3.x版本进行了改造,针对该问题做了优化,参见Dubbo 3 Spring相关优化。
但3.x做了大量重构,如果我们不想升级,应该怎么办呢?
其实根据上面提到的原因,我们可以自己定义一个后置处理器,拦截 dubbo 框架的 beans,并手动注入对应的配置。本质上来说,将之前来不及执行的注入代码提到前面去。这样,我们前面提到的「某些后置处理器执行时,在解决它的依赖时,会去创建 dubbo 服务的 bean,然后就会创建 ApplicationConfig 对象了」,就变成了「某些后置处理器执行时,在解决它的依赖时,会去创建 dubbo 服务的 bean,此时会被我们自定义的后置处理器拦截,并注入对应的dubbo配置,然后就会创建正确的 ApplicationConfig 对象」。
具体做法为:
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 { @Override public 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 DubboApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public 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