0
点赞
收藏
分享

微信扫一扫

关于dubbo 占位符无法解析问题


不知道大家有没有遇到过,你要开发一个新应用要使用 ​​dubbo​​​ 、 ​​apollo​​​ 等组件,在集成的过程中发现 ​​dubbo​​ 配置文件的占位符无法替换,wtf,配置明明和以前的项目一样,为啥就不行了。我前两天也遇到了这个问题,就一起来分析下。

文章目录

  • ​​简单配置介绍​​
  • ​​问题复现​​
  • ​​排查apollo​​
  • ​​排查PropertySourcesPlaceholderConfigurer​​
  • ​​排查dubbo​​
  • ​​思考​​
  • ​​验证想法​​
  • ​​两个项目为什么不一样​​
  • ​​解决办法​​
  • ​​降低版本​​
  • ​​注册MapperScannerConfigurer的bean​​
  • ​​自定义MapperScannerRegistrar 注解​​
  • ​​小彩蛋​​

简单配置介绍

项目大致使用了以下组件

  • 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​​ 配置是否加载即可

关于dubbo 占位符无法解析问题_xml


​debug​​​ 后可以发现,变量已经被 ​​apollo​​​ 加载到 ​​spring​​,那么问题还会出在哪里呢?

排查PropertySourcesPlaceholderConfigurer

大家应该知道 ​​spring​​​ 是使用 ​​PropertySourcesPlaceholderConfigurer​​​ 来对 ​​xml​​​等文件的占位符进行解析的,那么问题会是出在这里吗?继续进行 ​​debug​

关于dubbo 占位符无法解析问题_java_02


关于dubbo 占位符无法解析问题_spring_03


从截图中可以发现,值也是有的,问题出在哪呢?

排查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 占位符无法解析问题_spring_04


从截图中我们可以发现,​​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​​ 被提前初始化无疑了。

关于dubbo 占位符无法解析问题_xml_05

​PropertySourcesPlaceholderConfigurer​​​ 是在 ​​BeanFactoryPostProcessor​​​ 时触发的,也就是说,​​bean​​​在 ​​BeanFactoryPostProcessor​​触发之前初始化了,为什么会被提前初始化呢?

有了 ​​idea​​​ 就很简单,我们可以直接在 ​​RegistryConfig.setAddress​​ 打断点,看它的调用栈即可。

关于dubbo 占位符无法解析问题_xml_06


调用栈如下:

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);​​方法

关于dubbo 占位符无法解析问题_spring_07


最后导致在​​DefaultListableBeanFactory#doGetBeanNamesForType​​​ 遍历了所有的​​BeanDefinition​​ 导致提前初始化。

关于dubbo 占位符无法解析问题_spring_08

两个项目为什么不一样

最后发现在我的另外一个项目中 ​​mybatis-spring​​​ 的版本是 ​​2.0.1​​​ 而这个版本中是 ​​2.0.2​​​. 唯一不同的是 ​​2.0.2​​​ 版本中注册 ​​MapperScannerConfigurer​​​ 的​​BeanDefinition​​​ 时设置了​​processPropertyPlaceHolders​​​ 为​​true​​​。而 ​​2.0.1​​​ 默认为 ​​false​​ 的。

关于dubbo 占位符无法解析问题_xml_09

解决办法

知道原因了,解决办法也很简单。

降低版本

降低 ​​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_10

小彩蛋

其实之前我也遇到过一次 ​​dubbo​​​ 占位符无法解析的状况,经过细密的排查最后发现是使用了 ​​spring-boot​​​ 的热部署 ​​spring-boot-devtools​​​ 导致第二次加载不到资源。解决办法是去掉热部署的 ​​spring-boot-devtools​​ 就好了,可惜当时没有写文章记录下来。希望这个小彩蛋能帮助到你。

关注我,随时获取最新文章哦

关于dubbo 占位符无法解析问题_xml_11


举报

相关推荐

0 条评论