spring+zookeeper将zk当作配置中心,解析${}占位符原理

通常在spring项目中,我们是通过扫描resources下的配置文件,将配置文件中的配置解析出来,在容器启动时,通过invokeBeanFactoryPostProcessors来替换占位符的。
同理,当我们需要接入配置中心时,理论上,原理不变,我们也是在执行invokeBeanFactoryPostProcessors方法时来替换占位符,我们需要解决的是,将之前通过扫描配置文件解析出来配置,替换成读取zk,将其中的配置放到对应的地方,供替换时使用

在xml文件中,创建PreferencesPlaceholderConfigurer的bean,其properties属性引用properties加载类,这个加载类即为加载zk配置的自定义类

<bean id="prop" class="com.dome.config.ZkPropFactoryBean"></bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
  <property name="properties" ref="prop"/>
</bean>

容器初始化时,会加载xml,我们上面定义的bean会被加载成beanDefinition放到beanFactory中,在执行invokeBeanFactoryPostProcessors方法时,会从beanFactory中筛选各种BeanFactoryPostProcessor来执行,而我们定义的propertyConfigurer这个bean,他的类路径PreferencesPlaceholderConfigurer即为实现了BeanFactoryPostProcessor和PriorityOrdered的,所以同样会被筛选出来

public static void invokeBeanFactoryPostProcessors(
       ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
           
    ..此处省略大部分无关代码..
    
    List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    List<String> orderedPostProcessorNames = new ArrayList<>();
    List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    for (String ppName : postProcessorNames) {
       if (processedBeans.contains(ppName)) {
       }
       else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
           //我们定义的propertyConfigurer会在这里被筛选出来,筛选出来就提前实例化这个bean,
           //而且,我们自定义的propertyConfigurer这个bean,他的属性引用的是prop这个bean,所以这两个bean会在这一步一起初始化出来
          priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
       }
       else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
          orderedPostProcessorNames.add(ppName);
       }
       else {
          nonOrderedPostProcessorNames.add(ppName);
       }
    }
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    //这里就是替换占位符了,这里后面说完初始化在说
    invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
    
    ..此处省略大部分无关代码..
}

getBean这个方法太复杂了,就抽一点重点代码出来

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
       throws BeanCreationException {
..此处省略大部分无关代码..

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        //在实例化bean的时候,这里会填充bean的属性,我们定义的prop是propertyConfigurer的属性,所以在创建propertyConfigurer的时候,会先实例化prop
       populateBean(beanName, mbd, instanceWrapper);
       exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
       if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
          throw (BeanCreationException) ex;
       }
       else {
          throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
       }
    }
..此处省略大部分无关代码..
}

在初始化bean的时候,会将zk的bean也初始化

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.core.io.Resource;
public class ZkPropFactoryBean extends PropertiesFactoryBean implements FactoryBean<Properties>, InitializingBean {
    private Properties properties;
    
   public void setLocations(Resource... locations) {
       //这里就会初始化加载zk了
      ZookeeperPropertiesLoader.instance
    }
    //因为改bean继承了PropertiesFactoryBean所以实例化bean的时候还会走这里
    protected Properties createProperties() throws IOException {
        //将 ZkPropLoader.instance.getProperties()  获取到的zk配置,全部放到properties 中,
        //这样的话ZkPropFactoryBean在实例化完成后其properties属性中,放置的全部都是zk的配置
    }
}

这里是定义一个枚举,用来加载zk配置中心的配置

public enum ZkPropLoader {
    instance;

    //在这里连接zk,将其中的配置都放到properties中
    private Map<String, String> properties = new LinkedHashMap();
}

根据上面bean初始化流程,我们知道
1、容器先初始化propertyConfigurer,因为其中属性引用prop
2、实例化prop,在这里过程中,链接zk,并且将配置全都放到其中的properties中
3、上面的prop实例化结束后,prop会走factory.getObject()

@Override
@Nullable
public final Properties getObject() throws IOException {
    if (this.singleton) {
        //我们在刚才实例化时知道singleton是true而且singletonInstance会加载properties
       return this.singletonInstance;
    }
    else {
       return createProperties();
    }
}

4、所以propertyConfigurer加载完成后其properties属性也会是zk加载的内容

上面是bean初始化,zk初始化,的一系列动作,算是一个准备工作
接下来就是invokeBeanFactoryPostProcessors这个方法的invokeBeanFactoryPostProcessors了,这时候才正式开始解析占位符

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    try {
        //加载上面的Properties,即得到的zk配置
       Properties mergedProps = mergeProperties();

       // Convert the merged properties, if necessary.
       convertProperties(mergedProps);

       // 这里会吧beanfactory中的beanDefinition拿出来,解析每一个的占位符
       processProperties(beanFactory, mergedProps);
    }
    catch (IOException ex) {
       throw new BeanInitializationException("Could not load properties", ex);
    }
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
       StringValueResolver valueResolver) {

    BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

    String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
    for (String curName : beanNames) {
       // Check that we're not parsing our own bean definition,
       // to avoid failing on unresolvable placeholders in properties file locations.
       if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
          BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
          try {
              //在这里
             visitor.visitBeanDefinition(bd);
          }
          catch (Exception ex) {
             throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
          }
       }
    }
}
public void visitBeanDefinition(BeanDefinition beanDefinition) {
    visitParentName(beanDefinition);
    visitBeanClassName(beanDefinition);
    visitFactoryBeanName(beanDefinition);
    visitFactoryMethodName(beanDefinition);
    visitScope(beanDefinition);
    if (beanDefinition.hasPropertyValues()) {
        //这里是看你的beanDefinition中有没有属性值
       visitPropertyValues(beanDefinition.getPropertyValues());
    }
    if (beanDefinition.hasConstructorArgumentValues()) {
       ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
       visitIndexedArgumentValues(cas.getIndexedArgumentValues());
       visitGenericArgumentValues(cas.getGenericArgumentValues());
    }
}

@Nullable
protected Object resolveValue(@Nullable Object value) {
    //占位符会在这里
    else if (value instanceof TypedStringValue) {
       TypedStringValue typedStringValue = (TypedStringValue) value;
       String stringValue = typedStringValue.getValue();
       if (stringValue != null) {
          String visitedString = resolveStringValue(stringValue);
          typedStringValue.setValue(visitedString);
       }
    }
        return value;
}
@Nullable
protected String resolveStringValue(String strVal) {
    if (this.valueResolver == null) {
       throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
             "object into the constructor or override the 'resolveStringValue' method");
    }
    String resolvedValue = this.valueResolver.resolveStringValue(strVal);
    // Return original String if not modified.
    return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
@Override
@Nullable
public String resolveStringValue(String strVal) throws BeansException {
    String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
    if (trimValues) {
       resolved = resolved.trim();
    }
    return (resolved.equals(nullValue) ? null : resolved);
}
后面就是一点一点跟代码了
protected String parseStringValue(
       String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

    int startIndex = value.indexOf(this.placeholderPrefix);
    if (startIndex == -1) {
       return value;
    }

    StringBuilder result = new StringBuilder(value);
    while (startIndex != -1) {
       int endIndex = findPlaceholderEndIndex(result, startIndex);
       if (endIndex != -1) {
          String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
          String originalPlaceholder = placeholder;
          if (visitedPlaceholders == null) {
             visitedPlaceholders = new HashSet<>(4);
          }
          if (!visitedPlaceholders.add(originalPlaceholder)) {
             throw new IllegalArgumentException(
                   "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
          }
          // Recursive invocation, parsing placeholders contained in the placeholder key.
          placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
          // Now obtain the value for the fully resolved key...
          //这里就会在properties中取出我们的配置
          String propVal = placeholderResolver.resolvePlaceholder(placeholder);
          if (propVal == null && this.valueSeparator != null) {
             int separatorIndex = placeholder.indexOf(this.valueSeparator);
             if (separatorIndex != -1) {
                String actualPlaceholder = placeholder.substring(0, separatorIndex);
                String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                if (propVal == null) {
                   propVal = defaultValue;
                }
             }
          }
          //当我们的配置不为空,则接下来就是把beanDefinition中的属性占位符替换一下
          if (propVal != null) {
             // Recursive invocation, parsing placeholders contained in the
             // previously resolved placeholder value.
             propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
             result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
             if (logger.isTraceEnabled()) {
                logger.trace("Resolved placeholder '" + placeholder + "'");
             }
             startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
          }
          else if (this.ignoreUnresolvablePlaceholders) {
             // Proceed with unprocessed value.
             startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
          }
          else {
             throw new IllegalArgumentException("Could not resolve placeholder '" +
                   placeholder + "'" + " in value \"" + value + "\"");
          }
          visitedPlaceholders.remove(originalPlaceholder);
       }
       else {
          startIndex = -1;
       }
    }
    return result.toString();
}

上面忽略了很多过程,代码太多了,但是基本的流程有了,上面就已经吧所有的占位符替换完了,这样在后面实例化对应的bean时,就会使用zk中获取到的值了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值