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

探讨了在集成Dubbo和Apollo组件时,占位符无法正确解析的问题,分析了问题原因并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不知道大家有没有遇到过,你要开发一个新应用要使用 dubboapollo 等组件,在集成的过程中发现 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.factoriesApolloApplicationContextInitializer添加到 springbootApplicationContextInitializer

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 配置的 dubbodubbo 是通过在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 classRegistryConfig.class,继续进行 debug

在这里插入图片描述
从截图中我们可以发现,dubbo 此时通过解析 xml 获取的 address 的值还没有被PropertySourcesPlaceholderConfigurer 替换,怎么回事?

思考

此时,只有一种想法,难道该 bean 的初始化早于 PropertySourcesPlaceholderConfigurer 的替换?也就是说,RegistryConfig 被提前初始化了

验证想法

我们应该知道 dubbo的消费者即:ReferenceBean 实现了InitializingBean的接口的afterPropertiesSet方法,在所有变量完成设置后会回调该方法。

并且 ReferenceBean 实现了 FactoryBean 接口的 getObject 方法。该 bean 会使用动态代理 ProxyFactory.getProxy封装为一个对象,在我们调用某个方法时,在反射对象中使用 rpc 请求服务者返回执行结果,我们的ReferenceBean 中也持有RegistryConfig这个rpc配置对象。

所以当 springbean 属性被设置完成后,会调用afterPropertiesSet方法。
此时我在上面的PropertySourcesPlaceholderConfigurer#getPropertydubboReferenceBean#afterPropertiesSet 这里全部加上断点,果然发现 ReferenceBean 先执行 afterPropertiesSetPropertySourcesPlaceholderConfigurer 后执行 getProperty 变量的操作。此时可以断定,ReferenceBean 被提前初始化无疑了。

在这里插入图片描述

PropertySourcesPlaceholderConfigurer 是在 BeanFactoryPostProcessor 时触发的,也就是说,beanBeanFactoryPostProcessor触发之前初始化了,为什么会被提前初始化呢?
有了 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这里由于processPropertyPlaceHolderstrue导致进入processPropertyPlaceHolders方法,该方法调用了 applicationContext.getBeansOfType(PropertyResourceConfigurer.class);方法

在这里插入图片描述
最后导致在DefaultListableBeanFactory#doGetBeanNamesForType 遍历了所有的BeanDefinition 导致提前初始化。
在这里插入图片描述

两个项目为什么不一样

最后发现在我的另外一个项目中 mybatis-spring 的版本是 2.0.1 而这个版本中是 2.0.2. 唯一不同的是 2.0.2 版本中注册 MapperScannerConfigurerBeanDefinition 时设置了processPropertyPlaceHolderstrue。而 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 Cloud 中 `subscribed-services` 配置项的说明 在微服务架构中,Dubbo 提供了一种高效的服务治理机制来管理服务之间的依赖关系。`subscribed-services` 是 Dubbo Cloud 中的一个重要配置项,用于指定当前消费者实例需要订阅哪些服务。 #### 1. **功能描述** `subscribed-services` 主要作用是显式定义 Consumer 端所需订阅的服务列表。通过这种方式可以优化消费者的启动性能以及减少不必要的网络开销。当设置了该参数后,只有被列举出来的服务会被加载到客户端缓存中[^4]。 #### 2. **配置方式** 以下是常见的 YAML 文件中的配置方法: ```yaml dubbo: consumer: subscribed-services: service-a,service-b ``` 上述配置表示当前应用只会订阅名为 `service-a` 和 `service-b` 的两个服务。如果未设置此项,则默认会拉取注册中心上的所有可用服务并尝试建立连接[^5]。 #### 3. **实际案例分析** 假设有一个分布式系统包含多个模块化设计的应用程序组件,比如订单处理 (`order-service`)、库存查询 (`inventory-service`) 及支付网关(`payment-gateway`) 。对于负责前端展示逻辑的部分来说可能只需要调用前两者即可完成业务需求;此时就可以利用这个选项限定范围从而提升效率降低延迟时间[^6]: ```yaml spring: application: name: web-front-end dubbo: protocol: name: dubbo port: -1 registry: address: zookeeper://localhost:2181 consumer: subscribed-services: order-service, inventory-service ``` 以上例子展示了如何在一个基于 Spring Boot 构建的项目里合理运用这些特性以满足特定场景下的定制化需求[^7]。 #### 4. **注意事项** - 如果存在跨环境部署情况(如测试/生产),则需确保不同环境下对应的 values 正确无误以免引发异常行为。 - 当某些特殊情况下确实需要用到全局广播模式而非精确匹配时可考虑移除该项或者留空字符串("")作为占位符让框架自行决定策略[^8]。 ```java // Example of a basic configuration without specifying any services. @ConfigurationProperties(prefix = "dubbo.consumer") public class DubboConsumerConfig { private String subscribedServices; public void setSubscribedServices(String subscribedServices){ this.subscribedServices=subscribedServices; } } ``` ### 结论 综上所述,在大规模集群环境中适当调整此类高级别的控制手段有助于提高整体系统的稳定性和响应速度同时也能更好地适应未来扩展方向的变化趋势[^9]。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值