spring中controller service怎么调用的_想知道Spring配置中的占位符是怎么处理的吗

前言

之前我们在分析Spring别名配置的时候里面有一个配置用到了Spring的占位符配置,那这一篇我们先针对别名的占位符处理来做个简单的分析。

示例

<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder ignore-unresolvable="true" location="classpath:alias.properties"/>
<bean id="school" name="school1,school2" class="com.john.xmlconfig.SchoolName">
<property name="name" value="good school"/>
bean>

<alias name="school1" alias="${aliasName}"/>

beans>

我们看到在alias属性中我们加了aliasName这个占位符。这个占位符真正的值其实是在alias.properties这个配置文件里。

alias.properties

aliasName=seniorSchool

最后我们尝试通过seniorSchool这个别名能不能获取到school这个真正的Bean呢?答案是肯定的

school name:good school

下面我就来简单分析下Spring对别名占位符的处理过程。通过前面文章的分析我们知道别名配置在解析xml配置的时候会放到aliasMap这个内存Map中,也就是说占位符一开始有可能是直接没做处理就放进去了,我们通过调试来验证下我们的想法。

5bbd7863b204c0daec20432bb813dd0d.png

看到图中的aiasMap中占位符直接当作key存入了map中。那么接下来就有疑问了,Spring该怎么把这占位符替换成真正的属性值呢?

那就是Spring中强大的BeanFactoryPostProcessor.

原理

PropertySourcesPlaceholderConfigurer

Spring从3.1开始这个类就是默认的占位符处理类,我们看到它继承了PropertyResourceConfigurer,而且PropertyResourceConfigurer实现了BeanFactoryPostProcessor接口,所以Spring在启动的时候会调用BeanFactoryPostProcessor接口的postProcessBeanFactory方法。由于PropertySourcesPlaceholderConfigurer重写了postProcessBeanFactory方法,所以我们看看它的方法内部做了哪些处理:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) {
//添加环境PropertySources
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {@Override@Nullablepublic String getProperty(String key) {return this.source.getProperty(key);
}
}
);
}try {//添加本地配置PropertySources
PropertySource> localPropertySource =new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());if (this.localOverride) {this.propertySources.addFirst(localPropertySource);
}else {this.propertySources.addLast(localPropertySource);
}
}catch (IOException ex) {throw new BeanInitializationException("Could not load properties", ex);
}
}
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));this.appliedPropertySources = this.propertySources;
}

1.首先就是把各种配置包括环境配置和本地配置放入propertySources集合中。

2.调用processProperties方法开始处理属性配置。这一步很关键。

processProperties
/**
* Visit each bean definition in the given bean factory and attempt to replace ${...} property
* placeholders with values from the given properties.
*/
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {

propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);

StringValueResolver valueResolver = strVal -> {
String resolved = (this.ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (this.trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(this.nullValue) ? null : resolved);
};

doProcessProperties(beanFactoryToProcess, valueResolver);
}

我们看到这个方法的注释了吧,它会利用propertyResolver和StringValueResolver访问BeanFactory中的每个BeanDefinition然后把里面的${}格式的属性占位符给替换成给定的属性值。最后就会调用父类的PlaceholderConfigurerSupport的doProcessProperties方法。

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 {
//todo 这里去 注入 BeanDefinition 属性值 important 2020-09-12
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}

// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);

// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

我们貌似看到了最重要的一句代码beanFactoryToProcess.resolveAliases(valueResolver);。因为我们上文提到过别名一开始都存到了aliasMap中,那么必然Spring会去访问aliasMap解析把这个占位符替换成真正的值。我们看看resolveAliases方法,其实就是SimpleAliasRegistry中的方法。

SimpleAliasRegistry

resolveAliases
/**
* Resolve all alias target names and aliases registered in this
* factory, applying the given StringValueResolver to them.
*

The value resolver may for example resolve placeholders
* in target bean names and even in alias names.
* @param valueResolver the StringValueResolver to apply
*/


public void resolveAliases(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
synchronized (this.aliasMap) {
Map aliasCopy = new HashMap<>(this.aliasMap);
aliasCopy.forEach((alias, registeredName) -> {
String resolvedAlias = valueResolver.resolveStringValue(alias);
String resolvedName = valueResolver.resolveStringValue(registeredName);//名称与别名相同就移除呗!if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) {this.aliasMap.remove(alias);
}else if (!resolvedAlias.equals(alias)) {
String existingName = this.aliasMap.get(resolvedAlias);if (existingName != null) {if (existingName.equals(resolvedName)) {// Pointing to existing alias - just remove placeholderthis.aliasMap.remove(alias);return;
}throw new IllegalStateException("Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias +"') for name '" + resolvedName + "': It is already registered for name '" +
registeredName + "'.");
}
checkForAliasCircle(resolvedName, resolvedAlias);//todo 移除占位符 2020-11-20this.aliasMap.remove(alias);//放入真正的别名this.aliasMap.put(resolvedAlias, resolvedName);
}else if (!registeredName.equals(resolvedName)) {this.aliasMap.put(alias, resolvedName);
}
});
}
}

我们简单解析下这段代码:

1.加上同步访问aliasMap,把aliasMap拷贝到aliasCopy中。

2.遍历aliasCopy中的别名和名称集合,然后最重要的就是通过valueResolver去解析这两个字符串值。

3.如果解析出来的别名和原来的别名不一样,那么再获取解析后的别名对应的名称是否存在,如果存在并且等于解析前的别名对应的解析过后的名称,那就移除该别名并返回,如果不等于那说明别名已经被注册过了,会抛出异常。

4.检查别名和名称是否有循环引用。这里我们也简单分析下代码

checkForAliasCircle
/**
* Check whether the given name points back to the given alias as an alias
* in the other direction already, catching a circular reference upfront
* and throwing a corresponding IllegalStateException.
* @param name the candidate name
* @param alias the candidate alias
* @see #registerAlias
* @see #hasAlias
*/
protected void checkForAliasCircle(String name, String alias) {
if (hasAlias(alias, name)) {
throw new IllegalStateException("Cannot register alias '" + alias +
"' for name '" + name + "': Circular reference - '" +
name + "' is a direct or indirect alias for '" + alias + "' already");
}
}

/**
* Determine whether the given name has the given alias registered.
* @param name the name to check
* @param alias the alias to look for
* @since 4.2.1
*/
public boolean hasAlias(String name, String alias) {
for (Map.Entry entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {return true;
}
}
}return false;
}

简单说就是获取名称注册过的别名和传入的别名一样,或者注册过的别名当作名称后再获取它的别名是否和一开始传入的别名一样。

5.移除原来的占位符别名集合,放入解析过后的别名和名称。

总结

别名占位符处理涉及到了BeanFactoryPostProcessor,这个接口在Spring整个启动过程中扮演了很重要的角色,之后我会单独谈谈写文谈谈BeanFactoryPostProcessor接口。

接下来我会继续谈谈关于Spring Xml配置中的其他相关功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值