关于dubbo 占位符无法解析问题
Posted scx_white
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于dubbo 占位符无法解析问题相关的知识,希望对你有一定的参考价值。
不知道大家有没有遇到过,你要开发一个新应用要使用
dubbo
、apollo
等组件,在集成的过程中发现dubbo
配置文件的占位符无法替换,wtf,配置明明和以前的项目一样,为啥就不行了。我前两天也遇到了这个问题,就一起来分析下。
文章目录
简单配置介绍
项目大致使用了以下组件
- apollo 0.11.1-内部版本
- dubbo 2.6.0
- mybatis-plus-boot-starter 3.2.0
- mybatis-spring 2.0.2
- spring-boot 2.2.5.RELEASE
provider.xml
配置文件如下,其中xyuanDepServiceImpl
的类有使用注解@Service声明,所以在这里直接 ref
引用即可,并且dubbo.register.address
是在 apollo
中配置的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/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.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="xuan_yuan"/>
<dubbo:registry protocol="zookeeper" address="$dubbo.register.address"/>
<dubbo:protocol name="dubbo" port="20764"/>
<dubbo:service ref="xyuanDepServiceImpl" interface="com.sucx.admin.services.XyuanDepService"/>
</beans>
consumer.xml
内容如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/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.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:reference id="ssoInfoService" interface="com.tuya.sso.service.ISsoInfoService" check="false" lazy="true"/>
<dubbo:reference id="userSsoService" interface="com.tuya.sso.service.IUserSsoService" check="false" lazy="true"/>
</beans>
启动类
/**
* @author scx
*/
@SpringBootApplication(scanBasePackages = "com.sucx")
@EnableApolloConfig
@MapperScan(basePackages = "com.sucx.*.mapper")
@ImportResource(locations = "classpath:/dubbo/provider.xml", "classpath:/dubbo/consumer.xml")
public class AdminApplication
public static void main(String[] args)
SpringApplication.run(AdminApplication.class, args);
问题复现
执行 AdminApplication.main
方法抛出如下异常
2020-03-28 14:30:42.884 WARN 1647 --- [or-SendThread()] org.apache.zookeeper.ClientCnxn : Session 0x0 for server $dubbo.register.address:9090, unexpected error, closing socket connection and attempting reconnect
java.lang.IllegalArgumentException: named capturing group is missing trailing ''
at java.util.regex.Matcher.appendReplacement(Matcher.java:841) ~[na:1.8.0_201]
at java.util.regex.Matcher.replaceAll(Matcher.java:955) ~[na:1.8.0_201]
at java.lang.String.replaceAll(String.java:2223) ~[na:1.8.0_201]
at org.apache.zookeeper.ClientCnxn$SendThread.startConnect(ClientCnxn.java:997) ~[zookeeper-3.4.14.jar:3.4.14-4c25d480e66aadd371de8bd2fd8da255ac140bcf]
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1060) ~[zookeeper-3.4.14.jar:3.4.14-4c25d480e66aadd371de8bd2fd8da255ac140bcf]
很明显,xml
中的占位符没有被替换,此时我首先想到的是,apollo
难道没加载配置?
排查apollo
apollo 通过在 META-INF/spring.factories
将ApolloApplicationContextInitializer
添加到 springboot
的 ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=\\
com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
所以我们直接进入 ApplicationContextInitializer
进行 debug
查看dubbo.register.address
配置是否加载即可
debug
后可以发现,变量已经被 apollo
加载到 spring
,那么问题还会出在哪里呢?
排查PropertySourcesPlaceholderConfigurer
大家应该知道 spring
是使用 PropertySourcesPlaceholderConfigurer
来对 xml
等文件的占位符进行解析的,那么问题会是出在这里吗?继续进行 debug
从截图中可以发现,值也是有的,问题出在哪呢?
排查dubbo
经过上面两个分析,我们可以初步排除apollo和PropertySourcesPlaceholderConfigurer 的问题,唯有深入 dubbo
源码查看,由于我使用的 xml
配置的 dubbo
。dubbo
是通过在META-INF/spring.handlers
文件中注册 schema
的解析器,来将 xml
配置转换为spring
内部的 BeanDefinition
http\\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
进入 DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport
static
Version.checkDuplicate(DubboNamespaceHandler.class);
public void init()
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
发现 <dubbo:registry protocol="zookeeper" address="$dubbo.register.address"/>
是在 DubboBeanDefinitionParser
类中解析的,并且对应的bean class
为RegistryConfig.class
,继续进行 debug
从截图中我们可以发现,dubbo
此时通过解析 xml
获取的 address
的值还没有被PropertySourcesPlaceholderConfigurer
替换,怎么回事?
思考
此时,只有一种想法,难道该 bean
的初始化早于 PropertySourcesPlaceholderConfigurer
的替换?也就是说,RegistryConfig
被提前初始化了
验证想法
我们应该知道 dubbo
的消费者即:ReferenceBean
实现了InitializingBean
的接口的afterPropertiesSet
方法,在所有变量完成设置后会回调该方法。
并且
ReferenceBean
实现了FactoryBean
接口的getObject
方法。该bean
会使用动态代理ProxyFactory.getProxy
封装为一个对象,在我们调用某个方法时,在反射对象中使用rpc
请求服务者返回执行结果,我们的ReferenceBean
中也持有RegistryConfig
这个rpc
配置对象。
所以当 spring
的 bean
属性被设置完成后,会调用afterPropertiesSet
方法。
此时我在上面的PropertySourcesPlaceholderConfigurer#getProperty
和dubbo
的 ReferenceBean#afterPropertiesSet
这里全部加上断点,果然发现 ReferenceBean
先执行 afterPropertiesSet
, PropertySourcesPlaceholderConfigurer
后执行 getProperty
变量的操作。此时可以断定,ReferenceBean
被提前初始化无疑了。
PropertySourcesPlaceholderConfigurer
是在 BeanFactoryPostProcessor
时触发的,也就是说,bean
在 BeanFactoryPostProcessor
触发之前初始化了,为什么会被提前初始化呢?
有了 idea
就很简单,我们可以直接在 RegistryConfig.setAddress
打断点,看它的调用栈即可。
调用栈如下:
setAddress:123, RegistryConfig (com.alibaba.dubbo.config)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:332, BeanWrapperImpl$BeanPropertyHandler (org.springframework.beans)
processLocalProperty:458, AbstractNestablePropertyAccessor (org.springframework.beans)
setPropertyValue:278, AbstractNestablePropertyAccessor (org.springframework.beans)
setPropertyValue:266, AbstractNestablePropertyAccessor (org.springframework.beans)
setPropertyValues:97, AbstractPropertyAccessor (org.springframework.beans)
setPropertyValues:77, AbstractPropertyAccessor (org.springframework.beans)
applyPropertyValues:1732, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
populateBean:1444, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:594, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:517, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 700631078 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$181)
getSingleton:222, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:321, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:617, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:1250, AbstractApplicationContext (org.springframework.context.support)
beansOfTypeIncludingAncestors:378, BeanFactoryUtils (org.springframework.beans.factory)
afterPropertiesSet:129, ReferenceBean (com.alibaba.dubbo.config.spring)
invokeInitMethods:1855, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1792, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:595, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:517, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 700631078 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$181)
getSingleton:222, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:321, AbstractBeanFactory (org.springframework.beans.factory.support)
getTypeForFactoryBean:1643, AbstractBeanFactory (org.springframework.beans.factory.support)
getTypeForFactoryBean:895, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
isTypeMatch:613, AbstractBeanFactory (org.springframework.beans.factory.support)
doGetBeanNamesForType:533, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeanNamesForType:491, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:613, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:605, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:1242, AbstractApplicationContext (org.springframework.context.support)
processPropertyPlaceHolders:367, MapperScannerConfigurer (org.mybatis.spring.mapper)
postProcessBeanDefinitionRegistry:338, MapperScannerConfigurer (org.mybatis.spring.mapper)
invokeBeanDefinitionRegistryPostProcessors:275, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:125, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:706, AbstractApplicationContext (org.springframework.context.support)
refresh:532, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:747, SpringApplication (org.springframework.boot)
refreshContext:397, SpringApplication (org.springframework.boot)
run:315, SpringApplication (org.springframework.boot)
run:1226, SpringApplication (org.springframework.boot)
run:1215, SpringApplication (org.springframework.boot)
main:22, AdminApplication (com.tuya.admin)
最后我终于发现在MapperScannerConfigurer
这里由于processPropertyPlaceHolders
为true
导致进入processPropertyPlaceHolders
方法,该方法调用了 applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
方法
最后导致在DefaultListableBeanFactory#doGetBeanNamesForType
遍历了所有的BeanDefinition
导致提前初始化。
两个项目为什么不一样
最后发现在我的另外一个项目中 mybatis-spring
的版本是 2.0.1
而这个版本中是 2.0.2
. 唯一不同的是 2.0.2
版本中注册 MapperScannerConfigurer
的BeanDefinition
时设置了processPropertyPlaceHolders
为true
。而 2.0.1
默认为 false
的。
解决办法
知道原因了,解决办法也很简单。
降低版本
降低 mybatis-spring
的版本,很简单,就不再赘述
注册MapperScannerConfigurer的bean
还记得最上面我们的启动类的MapperScan
注解吗?去掉该注解,然后增加一个配置类
@Component
public class SpringBeans
@Bean
public MapperScannerConfigurer mapperScannerConfigurer()
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setProcessPropertyPlaceHolders(false);
configurer.setBasePackage("com.sucx.*.mapper");
return configurer;
自定义MapperScannerRegistrar 注解
自定义一个MapperScan注解,重写@Import
注解内的MapperScannerRegistrar
类,把processPropertyPlaceHolders
值设置为false
即可
小彩蛋
其实之前我也遇到过一次 dubbo
占位符无法解析的状况,经过细密的排查最后发现是使用了 spring-boot
的热部署 spring-boot-devtools
导致第二次加载不到资源。解决办法是去掉热部署的 spring-boot-devtools
就好了,可惜当时没有写文章记录下来。希望这个小彩蛋能帮助到你。
关注我,随时获取最新文章哦
以上是关于关于dubbo 占位符无法解析问题的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 Spring Boot 解析 JavaFX 应用程序的占位符
Spring boot 无法解析占位符 application.yml