dubbo註冊中心佔位符無法解析問題

yejg1212發表於2021-10-20

dubbo註冊中心佔位符無法解析問題

1、背景

最近搞了2個老專案,想把他們融合到一起。這倆專案情況簡介如下:

  • 專案一:基於SpringMVC + dubbo,配置讀取本地properties檔案,少量配置讀取apollo
  • 專案二:基於Springboot + dubbo,配置讀取apollo

本著就高不就低的原則,順帶就把他們拉平到基於springboot的了,沒想到這一番操作引入了一個折騰我2天的坑。

2、現象

專案合到一起後,處理掉各種報錯後,啟動,控制檯迅速列印出錯誤日誌:

Caused by: java.net.UnknownHostException: ${zk.address}
	at java.net.InetAddress.getAllByName0(InetAddress.java:1280)
	at java.net.InetAddress.getAllByName(InetAddress.java:1192)
	at java.net.InetAddress.getAllByName(InetAddress.java:1126)
	at java.net.InetAddress.getByName(InetAddress.java:1076)
	at io.netty.util.internal.SocketUtils$8.run(SocketUtils.java:156)

zk.address是寫在專案的spring-dubbo-common.xml中,配置內容如下:

<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 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="dc-dubbo">
    <dubbo:registry address="${zk.address}">
    <dubbo:protocol name="dubbo" port="21660" threadpool="fixed" threads="300">
</dubbo:protocol></dubbo:registry></dubbo:application></beans>

具體的值是配置在apollo上的。

3、問題排查

3.1、apollo配置問題?

首先想到的是,可能是apollo配置問題。就依次做了如下檢查:

  1. 檢查【src/main/resources/META-INF/app.properties】裡面的appId是否和apollo配置中心的一致
  2. 檢查apollo上的配置,是不是真的有zk.address
  3. 檢查下【C:\opt\data\ {appId}】目錄下的apollo配置快取,找一下是不是有zk.address的配置

這三點都檢查下來,發現配置讀取都正常。

3.2、 配置解析問題?

配置都正常,但是佔位符卻沒解析,那麼想到解析“模組”出了問題。

解析佔位符一般通過Spring的PropertySourcesPlaceholderConfigurer來處理。

在Apollo的原始碼中,已經註冊過PropertySourcesPlaceholderConfigurer這個類了,原始碼com.ctrip.framework.apollo.spring.spi.DefaultApolloConfigRegistrarHelper摘錄如下:

public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes attributes = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
    String[] namespaces = attributes.getStringArray("value");
    int order = attributes.getNumber("order");
    PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);

    Map<string, object=""> propertySourcesPlaceholderPropertyValues = new HashMap<>();
    propertySourcesPlaceholderPropertyValues.put("order", 0);
      
	// !! 這裡註冊了PropertySourcesPlaceholderConfigurer類 !!
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
        PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
      
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
        PropertySourcesProcessor.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
        ApolloAnnotationProcessor.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
        SpringValueProcessor.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(),
        SpringValueDefinitionProcessor.class);
    BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
        ApolloJsonValueProcessor.class);
  }

  @Override
  public int getOrder() {
    return Ordered.LOWEST_PRECEDENCE;
  }
}

然後我嘗試在PropertySourcesPlaceholderConfigurer裡面加斷點,看下是不是有進入解析替換配置。

從debug來看,配置解析也正常,能正確解析apollo上的配置。

到這裡,我有點納悶了,已經解析到了,咋還提示報錯了呢?難道是dubbo的問題?

3.3、dubbo自身的問題?

dubbo的配置,最終會封裝成物件。上面配置的zk.address是dubbo:registry的配置,最終會封裝成RegistryConfig物件。

類似的還有很多,比如:

  • ApplicationConfig:對應dubbo:application應用配置
  • ProtocolConfig:對應協議配置

於是,我在RegistryConfig#setAddress方法加了個斷點。看看物件構造時,傳入的address引數是多少。

看來,這個問題還得往前找原因。

當我繼續往下執行的時候,idea提示斷點進入了3.2節的org.springframework.context.support.PropertySourcesPlaceholderConfigurer#postProcessBeanFactory處。

也就是說,在Spring解析佔位符之前,dubbo的物件(RegistryConfig)就開始建立並初始化了。

3.4、物件建立時機問題

從前面的分析下來,問題原因算是找到了:

在Spring解析佔位符之前,dubbo的物件(RegistryConfig)就開始建立初始化了。導致後面引用的都是未替換佔位符的RegistryConfig物件。最終,專案啟動才報UnknownHostException: ${zk.address} 異常

再次debug走起,斷點停留在RegistryConfig#setAddress方法,看下呼叫棧:

setAddress:118, RegistryConfig (com.alibaba.dubbo.config), RegistryConfig.java
invoke0:-1, NativeMethodAccessorImpl (sun.reflect), NativeMethodAccessorImpl.java
invoke:62, NativeMethodAccessorImpl (sun.reflect), NativeMethodAccessorImpl.java
invoke:43, DelegatingMethodAccessorImpl (sun.reflect), DelegatingMethodAccessorImpl.java
invoke:498, Method (java.lang.reflect), Method.java
setValue:332, BeanWrapperImpl$BeanPropertyHandler (org.springframework.beans), BeanWrapperImpl.java
processLocalProperty:458, AbstractNestablePropertyAccessor (org.springframework.beans), AbstractNestablePropertyAccessor.java
setPropertyValue:278, AbstractNestablePropertyAccessor (org.springframework.beans), AbstractNestablePropertyAccessor.java
setPropertyValue:266, AbstractNestablePropertyAccessor (org.springframework.beans), AbstractNestablePropertyAccessor.java
setPropertyValues:97, AbstractPropertyAccessor (org.springframework.beans), AbstractPropertyAccessor.java
setPropertyValues:77, AbstractPropertyAccessor (org.springframework.beans), AbstractPropertyAccessor.java
applyPropertyValues:1732, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
populateBean:1444, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
doCreateBean:594, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
createBean:517, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
lambda$doGetBean$0:323, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
getObject:-1, 1713129148 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$195), Unknown Source
getSingleton:226, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support), DefaultSingletonBeanRegistry.java
doGetBean:321, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
getBeansOfType:621, DefaultListableBeanFactory (org.springframework.beans.factory.support), DefaultListableBeanFactory.java
getBeansOfType:1251, AbstractApplicationContext (org.springframework.context.support), AbstractApplicationContext.java
beansOfTypeIncludingAncestors:378, BeanFactoryUtils (org.springframework.beans.factory), BeanFactoryUtils.java
afterPropertiesSet:140, ReferenceBean (com.alibaba.dubbo.config.spring), ReferenceBean.java
invokeInitMethods:1855, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
initializeBean:1792, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
doCreateBean:595, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
createBean:517, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
lambda$doGetBean$0:323, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
getObject:-1, 1713129148 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$195), Unknown Source
getSingleton:226, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support), DefaultSingletonBeanRegistry.java
doGetBean:321, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
getTypeForFactoryBean:1646, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
getTypeForFactoryBean:895, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support), AbstractAutowireCapableBeanFactory.java
isTypeMatch:613, AbstractBeanFactory (org.springframework.beans.factory.support), AbstractBeanFactory.java
doGetBeanNamesForType:537, DefaultListableBeanFactory (org.springframework.beans.factory.support), DefaultListableBeanFactory.java
getBeanNamesForType:495, DefaultListableBeanFactory (org.springframework.beans.factory.support), DefaultListableBeanFactory.java
getBeansOfType:617, DefaultListableBeanFactory (org.springframework.beans.factory.support), DefaultListableBeanFactory.java
getBeansOfType:609, DefaultListableBeanFactory (org.springframework.beans.factory.support), DefaultListableBeanFactory.java
getBeansOfType:1243, AbstractApplicationContext (org.springframework.context.support), AbstractApplicationContext.java
processPropertyPlaceHolders:367, MapperScannerConfigurer (org.mybatis.spring.mapper), MapperScannerConfigurer.java
postProcessBeanDefinitionRegistry:338, MapperScannerConfigurer (org.mybatis.spring.mapper), MapperScannerConfigurer.java
invokeBeanDefinitionRegistryPostProcessors:280, PostProcessorRegistrationDelegate (org.springframework.context.support), PostProcessorRegistrationDelegate.java
invokeBeanFactoryPostProcessors:126, PostProcessorRegistrationDelegate (org.springframework.context.support), PostProcessorRegistrationDelegate.java
invokeBeanFactoryPostProcessors:707, AbstractApplicationContext (org.springframework.context.support), AbstractApplicationContext.java
refresh:533, AbstractApplicationContext (org.springframework.context.support), AbstractApplicationContext.java
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context), ServletWebServerApplicationContext.java
refresh:758, SpringApplication (org.springframework.boot), SpringApplication.java
refresh:750, SpringApplication (org.springframework.boot), SpringApplication.java
refreshContext:397, SpringApplication (org.springframework.boot), SpringApplication.java
run:315, SpringApplication (org.springframework.boot), SpringApplication.java
main:36, DcRemoteApplication (com.yc.dc.remote), DcRemoteApplication.java

從呼叫棧,發現一點奇怪的呼叫路徑:

在org.mybatis.spring.mapper.MapperScannerConfigurer#processPropertyPlaceHolders方法中,呼叫了applicationContext.getBeansOfType(PropertyResourceConfigurer.class); 從而引發了後面的一些列的物件建立在PropertyResourceConfigurer之前。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) { // true
      processPropertyPlaceHolders();
    }
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));  
  }
  
  private void processPropertyPlaceHolders() {
    // 這裡調 getBeansOfType(PropertyResourceConfigurer.class)
    Map<string, propertyresourceconfigurer=""> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
    // ...此處省略若干程式碼...  
  }
  
  // ...此處省略若干程式碼...
}

【applicationContext.getBeansOfType(PropertyResourceConfigurer.class);】執行過程中

Spring一路執行到org.springframework.beans.factory.support.DefaultListableBeanFactory#doGetBeanNamesForType,

並在方法內,迴圈所有beanDefinitionNames,逐一判斷beanDefinition是不是PropertyResourceConfigurer型別的(isTypeMatch方法)

判斷的時候,就需要從Spring容器中取這個bean,取著取著發現沒有,就開始doCeateBean-->invokeInitMethods-->afterPropertiesSet 了。

在建立dubbo消費者的時候,消費者介面被封裝成ReferenceBean,在其afterPropertiesSet方法中,依賴RegistryConfig物件,從而依賴註冊中心配置的地址。

而這些,都是發生在PropertyResourceConfigurer建立之前。

此處特別說明,我專案裡面使用的mybatis-spring是2.0.3版本

4、 解決方法

4.1、方法一

前面分析了,MapperScannerConfigurer物件的boolean屬性processPropertyPlaceHolders為true,導致執行了processPropertyPlaceHolders()方法。

那麼,據此可以自定義一個MapperScannerConfigurer物件,設定processPropertyPlaceHolders為false。

// 去掉 @MapperScan 註解,通過下面的方式設定MapperScannerConfigurer
@Bean
public MapperScannerConfigurer dsCrmMapperScannerConfigurer() {
	MapperScannerConfigurer configurer = new MapperScannerConfigurer();
	configurer.setProcessPropertyPlaceHolders(false);
	configurer.setBasePackage("com.yc.dc.mapper");
	configurer.setSqlSessionFactoryBeanName("dsCrmSqlSessionFactory");
	return configurer;
}

通過這種方式試了下,發現果然可行。

為啥可行? 究其原因如下:

MapperScannerConfigurer一般通過MapperScannerRegistrar引入進來。

mybatis-spring 是從2.0.2版本開始,引入MapperScannerConfigurer的時候,設定了processPropertyPlaceHolders值為true。

才會在掃描mapper之前,調了一下 processPropertyPlaceHolders()方法。

// org.mybatis.spring.annotation.MapperScannerRegistrar,mybatis-spring 2.0.2及以上版本
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true); // 看這裡!!

    Class<!--? extends Annotation--> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
    // .....
}


// org.mybatis.spring.annotation.MapperScannerRegistrar,mybatis-spring 2.0.1及以下版本
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // ClassPathMapperScanner裡面就沒有getBeansOfType(PropertyResourceConfigurer.class)
    Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
    Class<!--? extends Annotation--> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }
	// .....
}

4.2、方法二

降低mybatis-spring的版本到 2.0.1 或者 更低版本。

5、 未完待續

到這裡,問題就查清楚了,並且解決辦法也有了。但真的結束了嗎?
木有,合併前的老專案二使用的就是mybatis-spring-2.0.3.jar,但是他沒有報【UnknownHostException: ${zk.address}】,也沒有自定義MapperScannerConfigurer設定processPropertyPlaceHolders為false
那為什麼?
下篇文章分析~

相關文章