@ConfigurationProperties 配置詳解


文章轉自 https://blog.csdn.net/qq_26000415/article/details/78942494

前言
新的一年到了,在這里先祝大家新年快樂.我們在上一篇spring boot 源碼解析12-servlet容器的建立 中 分析 ServerProperties時,發現其類上有@ConfigurationProperties 注解,加上該注解后,就會注入在application.properties中server開頭的屬性,那么它是怎么生效的呢?我們這篇文章就來分析一下.這篇文章內容比較長,大家慢慢看…

@ConfigurationProperties 使用方式
我們首先聲明實體類,用於屬性的注入.代碼如下:

public class People {

private String name;

private Integer age;

private List<String> address;

private Phone phone;

// get set 忽略,自己加上即可..
}


public class Phone {

private String number;

// get set 忽略,自己加上即可..

}
在application.properties 中加入如下配置:

com.example.demo.name=${aaa:hi}
com.example.demo.age=11
com.example.demo.address[0]=北京
com.example.demo.address[1]=上海
com.example.demo.address[2]=廣州
com.example.demo.phone.number=1111111
@ConfigurationProperties 注解支持兩種方式.

加在類上,需要和@Component注解,結合使用.代碼如下:

@Component
@ConfigurationProperties(prefix = "com.example.demo")
public class People {

private String name;

private Integer age;

private List<String> address;

private Phone phone;
}
通過@Bean的方式進行聲明,這里我們加在啟動類即可,代碼如下:

@SpringBootApplication
public class DemoApplication {

@Bean
@ConfigurationProperties(prefix = "com.example.demo")
public People people() {

return new People();
}

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}


}
這里我們使用第2種,原因是這樣好debug,看源碼…

我們再寫一個Controller進行測試一下吧.代碼如下:

@RestController
public class TestController {

@Autowired
private People people;

@RequestMapping("/get_name")
public String getName() {

return people.getName();
}

@RequestMapping("/get_address")
public List<String> getAddress() {

return people.getAddress();
}

@RequestMapping("/get_phone_numer")
public String getNumber() {

return people.getPhone().getNumber();
}
}
訪問 /get_name,其返回值如下:

hi

訪問 /get_address,其返回值如下:

[“北京”,”上海”,”廣州”]

訪問 get_phone_numer,其返回值如下:

1111111

使用方式就介紹完了,接下來,我們就來看看spring 是如何處理的吧.

解析
我們應該知道了@ConfigurationProperties 和 @Bean 或者 @Component 等只要能生成spring bean 的注解 結合起來使用,這樣的話,當其他類注入該類時,就會觸發該類的加載過程,那么在加載過程中,會調用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization.因此會觸發ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization的調用,這里就是我們的起點.

ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization 代碼如下:

public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 1. 獲得類上的@ConfigurationProperties注解,如果注解存在,則調用postProcessBeforeInitialization 進行處理
ConfigurationProperties annotation = AnnotationUtils
.findAnnotation(bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
// 2. 尋找工廠方法上是否有@ConfigurationProperties 注解,如果存在的話,則調用postProcessBeforeInitialization進行處理
annotation = this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
return bean;
}
2件事:

獲得類上的@ConfigurationProperties注解,如果注解存在,則調用postProcessBeforeInitialization 進行處理
尋找工廠方法上是否有@ConfigurationProperties 注解,如果存在的話,則調用postProcessBeforeInitialization進行處理.對應的是@Bean的方式.
不管怎么樣,最終都會調用ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization.代碼如下:

private void postProcessBeforeInitialization(Object bean, String beanName,
ConfigurationProperties annotation) {
Object target = bean;
// 1. 實例化PropertiesConfigurationFactory,該類實現了FactoryBean, MessageSourceAware, InitializingBean 接口,並進行一些屬性的設置
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
target);
factory.setPropertySources(this.propertySources);
factory.setValidator(determineValidator(bean));
// If no explicit conversion service is provided we add one so that (at least)
// comma-separated arrays of convertibles can be bound automatically
// 由於conversionService 一直為 null,因此會調用getDefaultConversionService
factory.setConversionService(this.conversionService == null
? getDefaultConversionService() : this.conversionService);
if (annotation != null) {
// 2. 如果注解存在,這是肯定的,不然也不會執行該方法,則根據@ConfigurationProperties的值進行配置
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
factory.setExceptionIfInvalid(annotation.exceptionIfInvalid());
factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
if (StringUtils.hasLength(annotation.prefix())) {
// 2.1 如果配置了prefix,或者value 值,則設置TargetName
factory.setTargetName(annotation.prefix());
}
}
try {
// 3. 進行綁定
factory.bindPropertiesToTarget();
}
catch (Exception ex) {
String targetClass = ClassUtils.getShortName(target.getClass());
throw new BeanCreationException(beanName, "Could not bind properties to "
+ targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
}
}
3件事:

實例化PropertiesConfigurationFactory,該類實現了FactoryBean, MessageSourceAware, InitializingBean 接口,並進行一些屬性的設置.

將ConfigurationPropertiesBindingPostProcessor中的propertySources賦值給PropertiesConfigurationFactory
通過調用determineValidator方法,生成Validator,並進行賦值.代碼如下:

private Validator determineValidator(Object bean) {
// 1. 獲得validator
Validator validator = getValidator();
// 2. 如果validator不等於null並且該Validator 支持該bean的話
boolean supportsBean = (validator != null && validator.supports(bean.getClass()));
if (ClassUtils.isAssignable(Validator.class, bean.getClass())) {// 3 如果當前類為Validator的子類
// 3.1 如果supportsBean,則實例化ChainingValidator
if (supportsBean) {
return new ChainingValidator(validator, (Validator) bean);
}
// 3.2 否則強轉為Validator
return (Validator) bean;
}
// 4. 最后,如果supportsBean 則 返回Validator 否則 返回null
return (supportsBean ? validator : null);
}
4件事:

調用getValidator方法獲得Validator.代碼如下:

private Validator getValidator() {
// 1. 由之前可知,該validator 一直都是null.
if (this.validator != null) {
return this.validator;
}
// 2. 如果localValidator 等於null並且是jsr303環境的話,則實例化ValidatedLocalValidatorFactoryBean,並賦值給localValidator,lazy-init
// ValidatedLocalValidatorFactoryBean 實現了ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean,SmartValidator, javax.validation.Validator
if (this.localValidator == null && isJsr303Present()) {
this.localValidator = new ValidatedLocalValidatorFactoryBean(
this.applicationContext);
}
return this.localValidator;
}
如果validator 不等於null,則直接返回.可是該validator是一直等於null.原因如下:
ConfigurationPropertiesBindingPostProcessor 實現了InitializingBean接口,因此為調用其afterPropertiesSet方法,在該方法,有如下片段:

if (this.validator == null) {
// 2. 嘗試獲得id 為 configurationPropertiesValidator,type為Validator 的bean,此時是沒有獲取到
this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class);
}
會嘗試從beanFactory中獲得id 為 configurationPropertiesValidator,type 為 Validator的bean,可是默認情況下,是不存在的.

如果localValidator 等於null並且是jsr303環境的話,則實例化ValidatedLocalValidatorFactoryBean,並賦值給localValidator,這是一個lazy-init,ValidatedLocalValidatorFactoryBean 實現了ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean,SmartValidator, javax.validation.Validator接口.
如果不等於null,則直接返回
如果validator不等於null並且該Validator 支持該bean的話,則supportsBean等於true,否則為false.
如果當前類為Validator的子類

如果supportsBean為true,則實例化ChainingValidator,則初始化ChainingValidator.進行返回
否則強轉為Validator,進行返回
最后,如果supportsBean 則 返回Validator 否則 返回null
如果ConfigurationPropertiesBindingPostProcessor#conversionService 等於null,則調用getDefaultConversionService獲得默認的ConversionService.否則,直接將本類的conversionService 賦值給PropertiesConfigurationFactory 的ConversionService.還是由於conversionService一直為 null,因此會調用getDefaultConversionService.代碼如下:

private ConversionService getDefaultConversionService() {
// 又是lazy-init 風格
// 1. 如果defaultConversionService 等於null,則意味着是第一次調用
if (this.defaultConversionService == null) {
// 1.1 實例化DefaultConversionService
DefaultConversionService conversionService = new DefaultConversionService();
// 1.2 調用autowireBean進行注入依賴,此時會注入converters,genericConverters
this.applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
// 1.3 遍歷converters,genericConverters 依次加入到conversionService的converters中
for (Converter<?, ?> converter : this.converters) {
conversionService.addConverter(converter);
}
for (GenericConverter genericConverter : this.genericConverters) {
conversionService.addConverter(genericConverter);
}
// 1.4 賦值給defaultConversionService
this.defaultConversionService = conversionService;
}
// 2. 如果不等於null,則直接返回
return this.defaultConversionService;
}
2件事:

如果defaultConversionService 等於null,則意味着是第一次調用,又是lazy-init 風格.

實例化DefaultConversionService
調用autowireBean進行注入依賴,此時會注入converters,genericConverters
遍歷converters,genericConverters 依次加入到conversionService的converters中
賦值給defaultConversionService
如果不等於null,則直接返回
如果注解存在,這是肯定的,不然也不會執行該方法,則根據@ConfigurationProperties的值進行配置

如果配置了prefix,或者value 值,則設置TargetName.這個后面解析的時候會用到該值.
調用PropertiesConfigurationFactory#bindPropertiesToTarget,進行綁定
PropertiesConfigurationFactory#bindPropertiesToTarget 代碼如下:

public void bindPropertiesToTarget() throws BindException {
// 1.首先判斷propertySources是否為null,如果為null的話,拋出異常.一般不會為null的
Assert.state(this.propertySources != null, "PropertySources should not be null");
try {
if (logger.isTraceEnabled()) {
logger.trace("Property Sources: " + this.propertySources);

}
// 2. 將hasBeenBound 設為true
this.hasBeenBound = true;
// 3. 調用doBindPropertiesToTarget
doBindPropertiesToTarget();
}
catch (BindException ex) {
if (this.exceptionIfInvalid) {
throw ex;
}
PropertiesConfigurationFactory.logger
.error("Failed to load Properties validation bean. "
+ "Your Properties may be invalid.", ex);
}
}
3件事:

首先判斷propertySources是否為null,如果為null的話,拋出異常.一般不會為null的.因為該類在實例化的時候,已經對其進行賦值了
將hasBeenBound 設為true
調用doBindPropertiesToTarget.代碼如下:

private void doBindPropertiesToTarget() throws BindException {
// 1. 初始化RelaxedDataBinder 並進行設置一下屬性. // target = SpringApplication.這樣RelaxedDataBinder也就持有了SpringApplication
RelaxedDataBinder dataBinder = (this.targetName != null
? new RelaxedDataBinder(this.target, this.targetName)
: new RelaxedDataBinder(this.target));
// 對於當前場景來說validator還是為null的,在 ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization 中,該validator為ValidatedLocalValidatorFactoryBean
if (this.validator != null
&& this.validator.supports(dataBinder.getTarget().getClass())) {
dataBinder.setValidator(this.validator);
}
if (this.conversionService != null) {
// 持有了一系列的轉換器
dataBinder.setConversionService(this.conversionService);
}
dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);
dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
// 2. 擴展點,空實現
customizeBinder(dataBinder);
// 3. 獲得relaxedTargetNames,對於當前來說,其值為-->spring.main,也就是獲得@ConfigurationProperties中配置的prefix
Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
// 4. 通過遍歷target的屬性,這里的target為SpringApplication.然后將SpringApplication的屬性按照單詞划分的規則,與relaxedTargetNames進行拼接
// 舉例說明:SpringApplication中有一個logStartupInfo屬性,則拆分為log-startup-info,然后與spring.main拼接為
// spring.main.log-startup-info 和 spring.main_log-startup-info
// 通過拼接生成key
Set<String> names = getNames(relaxedTargetNames);
// 5. 生成PropertyValues,此時就已經將配置文件中的占位符解析完了
PropertyValues propertyValues = getPropertySourcesPropertyValues(names,
relaxedTargetNames);
// 6. 調用bind,進行綁定
dataBinder.bind(propertyValues);
if (this.validator != null) {
dataBinder.validate();
}
// 7. 檢查在綁定過程中是否出現異常,如果有的話,拋出BindException
checkForBindingErrors(dataBinder);
}
7件事:

初始化RelaxedDataBinder 並進行設置一下屬性,target = People.這樣RelaxedDataBinder也就持有了People.

如果validator不等於null,並且validator支持該類型的話,則設置RelaxedDataBinder的Validator,對於當前場景來說,是validator.
如果conversionService 不等於null,則設置ConversionService,這樣RelaxedDataBinder就持有了一系列的轉換器
設置AutoGrowCollectionLimit 為Integer.MAX_VALUE,該屬性在處理集合屬性注入時會用到
設置是否忽略嵌套屬性,默認是false.
設置是否忽略不正確的屬性,默認是false
設置是否忽略未知的子彈,默認是true
調用customizeBinder,這個擴展點,默認是空實現
調用getRelaxedTargetNames,對於當前來說,其值為–>com.example.demo,也就是獲得@ConfigurationProperties中配置的prefix
通過遍歷target的屬性,這里的target為People.然后將People的屬性按照單詞划分的規則,與relaxedTargetNames進行拼接.舉例說明: People中有一個name屬性,則拆分后為name,然后與com.example.demo拼接為com.example.demo.name
調用getPropertySourcesPropertyValues,生成PropertyValues,在這步完成了占位符解析.這個步驟很關鍵,我們在第4點中進行分析.
調用bind,進行綁定
檢查在綁定過程中是否出現異常,如果有的話,拋出BindException
getPropertySourcesPropertyValues.代碼如下:

private PropertyValues getPropertySourcesPropertyValues(Set<String> names,
Iterable<String> relaxedTargetNames) {
// 1. 根據names和relaxedTargetNames 生成PropertyNamePatternsMatcher
PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names,
relaxedTargetNames);
// 2. 返回PropertySourcesPropertyValues
return new PropertySourcesPropertyValues(this.propertySources, names, includes,
this.resolvePlaceholders);
}
根據names和relaxedTargetNames 生成PropertyNamePatternsMatcher.代碼如下:

private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names,
Iterable<String> relaxedTargetNames) {
// 1. 如果ignoreUnknownFields 並且 target 不是map的子類,則返回DefaultPropertyNamePatternsMatcher,在@ConfigurationProperties中,ignoreUnknownFields默認是true
if (this.ignoreUnknownFields && !isMapTarget()) {
// Since unknown fields are ignored we can filter them out early to save
// unnecessary calls to the PropertySource.
return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names);
}
// 2. 如果relaxedTargetNames 不等於null,則通過對relaxedTargetNames去重后,返回DefaultPropertyNamePatternsMatcher
if (relaxedTargetNames != null) {
// We can filter properties to those starting with the target name, but
// we can't do a complete filter since we need to trigger the
// unknown fields check
Set<String> relaxedNames = new HashSet<String>();
for (String relaxedTargetName : relaxedTargetNames) {
relaxedNames.add(relaxedTargetName);
}
return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true,
relaxedNames);
}
// Not ideal, we basically can't filter anything
// 3. 返回默認的
return PropertyNamePatternsMatcher.ALL;
}
3件事:

如果ignoreUnknownFields 並且 target 不是map的子類,則返回DefaultPropertyNamePatternsMatcher,在@ConfigurationProperties中,ignoreUnknownFields默認是true.在此時,由於target 為People,因此返回DefaultPropertyNamePatternsMatcher.
如果relaxedTargetNames 不等於null,則通過對relaxedTargetNames去重后,返回DefaultPropertyNamePatternsMatcher
返回默認的
返回PropertySourcesPropertyValues.其類圖如下:

 

其構造器如下:

PropertySourcesPropertyValues(PropertySources propertySources,
Collection<String> nonEnumerableFallbackNames,
PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) {
Assert.notNull(propertySources, "PropertySources must not be null");
Assert.notNull(includes, "Includes must not be null");
this.propertySources = propertySources;
this.nonEnumerableFallbackNames = nonEnumerableFallbackNames;
this.includes = includes;
this.resolvePlaceholders = resolvePlaceholders;
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
propertySources);
for (PropertySource<?> source : propertySources) {
processPropertySource(source, resolver);
}
}
3件事:

屬性賦值.
實例化PropertySourcesPropertyResolver
遍歷propertySources,依次調用processPropertySource.代碼如下:

private void processPropertySource(PropertySource<?> source,
PropertySourcesPropertyResolver resolver) {
if (source instanceof CompositePropertySource) {
processCompositePropertySource((CompositePropertySource) source, resolver);
}
else if (source instanceof EnumerablePropertySource) {
processEnumerablePropertySource((EnumerablePropertySource<?>) source,
resolver, this.includes);
}
else {
processNonEnumerablePropertySource(source, resolver);
}
}
如果PropertySource是CompositePropertySource的子類,則調用processCompositePropertySource方法,而該方法最終還是調用了processPropertySource,做遞歸處理.
如果PropertySource是EnumerablePropertySource的子類,則調用processEnumerablePropertySource.這里需要說明一下,我們是配置在application.properties中,那么其PropertySource 為 PropertiesPropertySource,是EnumerablePropertySource的子類,其繼承結構如下:


因此,關於配置文件屬性的注入,最終會在這里執行.

否則,調用processNonEnumerablePropertySource.
我們重點來看processEnumerablePropertySource,代碼如下:

private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes) {
if (source.getPropertyNames().length > 0) {
for (String propertyName : source.getPropertyNames()) {
if (includes.matches(propertyName)) {// 如果存在的話,則加入到propertyValues中
Object value = getEnumerableProperty(source, resolver, propertyName);
putIfAbsent(propertyName, value, source);
}
}
}
}
思路很簡單,

首先判斷source中是否有屬性的配置,如果有的話,則依次遍歷之
在遍歷過程中,會調用PropertyNamePatternsMatcher#matches 判斷是否匹配.這里說明一下,這里使用的是DefaultPropertyNamePatternsMatcher,其matches 會依次遍歷其內部的names 看是否與傳入的propertyName 匹配,這里的names 就是在實例化時傳入的com.example.demo.name等之類的東西.

如果匹配的話,則調用getEnumerableProperty 獲得值.代碼如下:

private Object getEnumerableProperty(EnumerablePropertySource<?> source,
PropertySourcesPropertyResolver resolver, String propertyName) {
try {
if (this.resolvePlaceholders) {
return resolver.getProperty(propertyName, Object.class);
}
}
catch (RuntimeException ex) {
// Probably could not resolve placeholders, ignore it here
}
return source.getProperty(propertyName);
}
如果resolvePlaceholders 為true,則調用PropertySourcesPropertyResolver#getProperty 處理,由於resolvePlaceholders 默認為true,因此一般都會執行這里.

否則,直接從EnumerablePropertySource 獲取值即可.

調用putIfAbsent 將值,屬性名,保存到propertyValues 中.
其中2.1 會調用如下代碼:

public <T> T getProperty(String key, Class<T> targetValueType) {
return getProperty(key, targetValueType, true);
}
最終調用如下代碼:

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}
3件事

如果propertySources 不等於null,則依次遍歷propertySources,進行處理

通過調用PropertySource#getProperty進行獲取

如果獲取到值的話,

如果resolveNestedPlaceholders(這個一般都是true) 並且value 為String,則調用resolveNestedPlaceholders處理占位符–>${},一般這個步驟都會執行的.
打印日志
嘗試對其進行轉換.
如果經過第1步處理,還是沒找到的話,則直接返回null
1.1.1.1 最終會調用如下方法.代碼如下:

public String resolvePlaceholders(String text) {
if (this.nonStrictHelper == null) {
this.nonStrictHelper = createPlaceholderHelper(true);
}
return doResolvePlaceholders(text, this.nonStrictHelper);
}
如果nonStrictHelper等於null,則調用createPlaceholderHelper進行實例化.lazy-init 風格.
調用AbstractPropertyResolver#doResolvePlaceholders.代碼如下:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
return getPropertyAsRawString(placeholderName);
}
});
}
這里直接調用了第一步實例化的PropertyPlaceholderHelper的replacePlaceholders進行處理,同時實例化了一個PlaceholderResolver,該類在獲取值的時候會用到,這個后面會有介紹.PropertyPlaceholderHelper#replacePlaceholders 代碼如下:

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
最終調用如下代碼:

protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
// 1. 通過String#indexOf 獲取前綴(一般都是${)的下標
int startIndex = value.indexOf(this.placeholderPrefix);
// 2 如果存在
while (startIndex != -1) {
// 2.1 獲得后綴,此時獲得是最小的后綴,嵌套處理
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {// 3 如果endIndex 存在,
// 3.1 通過字符串的截取獲得占位符,如${placeholder},那么此時獲得的是placeholder
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
// 3.2 進行循環引用的檢查,如果存在,則拋出IllegalArgumentException
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
// 3.3 遞歸處理
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
// 3.4 進行解析占位符
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 3.5 如果propVal 不等於null並且 valueSeparator(默認為 :)不等於null,則此時意味有默認值,
// 那么此時調用placeholderResolver#resolvePlaceholder 進行解析,如果解析失敗的話,則返回默認值
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;
}
}
}
// 3.6
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
// 3.6.1 如果propVal 不等於null,則意味着解析成功,則繼續遞歸處理,處理完后,進行替換,

propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
// 進行替換
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
// 重新計算startIndex
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
// 3.6.2 如果沒有解析成功並且ignoreUnresolvablePlaceholders,則重新計算startIndex
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
// 3.6.3 拋出IllegalArgumentException
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
// 3.7 從visitedPlaceholders 刪除.該算法有點類似dfs.
visitedPlaceholders.remove(originalPlaceholder);
}
else {
// 2.2 將startIndex 設為-1,則意味着已經處理完了
startIndex = -1;
}
}
return result.toString();
}
3件事:

通過String#indexOf 獲取前綴(一般都是${)的下標
如果存在

獲得后綴,此時獲得是最小的后綴,嵌套處理
如果endIndex 存在,

通過字符串的截取獲得占位符,如${placeholder},那么此時獲得的是placeholder
進行循環引用的檢查,如果存在,則拋出IllegalArgumentException
調用parseStringValue,進行遞歸處理.
調用PlaceholderResolver#resolvePlaceholder進行解析占位符
如果propVal 不等於null並且 valueSeparator(默認為 :)不等於null,則此時意味有默認值,那么此時調用placeholderResolver#resolvePlaceholder 進行解析,如果解析失敗的話,則返回默認值
如果propVal 不等於null,則意味着解析成功,則繼續遞歸處理,處理完后,進行替換,重新計算startIndex
如果沒有解析成功並且ignoreUnresolvablePlaceholders,則重新計算startIndex
其他情況下,則拋出異常
從visitedPlaceholders 刪除.該算法有點類似dfs.
如果不存在,則將startIndex 設為-1,則意味着已經處理完了.
關於占位符的處理,集合,對象導航,屬性轉換的處理,我們這里先不解析,我們先解析最簡單的情況,為People注入name 屬性.並且配置文件中的配置如下:

com.example.demo.name=hi

視線回到PropertiesConfigurationFactory#doBindPropertiesToTarget中來,此時執行完了getPropertySourcesPropertyValues,接下來就該執行第6步,調用DataBinder#bind進行綁定.這里還是假設我們只配置了com.example.demo.name=hi. 代碼如下:

public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs);
doBind(mpvs);
}
調用DataBinder#doBind,代碼如下:

protected void doBind(MutablePropertyValues mpvs) {
// 1. 檢查是否存在不允許的字段存在,如果存在則刪除
checkAllowedFields(mpvs);
// 2.檢查是否存在Required 字段缺失的情況
checkRequiredFields(mpvs);
// 3. 進行注入
applyPropertyValues(mpvs);
}
3件事:

檢查是否存在不允許的字段存在,如果存在則刪除
檢查是否存在Required 字段缺失的情況,如果存在,則拋出異常
調用applyPropertyValues進行注入.代碼如下:

protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
// 1. 進行注入
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
// 2. 如果拋出異常,則記錄異常
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
2件事:

進行注入.

調用getPropertyAccessor 獲得ConfigurablePropertyAccessor.代碼如下:

protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
調用getInternalBindingResult,獲得AbstractPropertyBindingResult.代碼如下:

protected AbstractPropertyBindingResult getInternalBindingResult() {
// 1. 同樣是lazy-init,當第一次調用時 ,調用initBeanPropertyAccess 進行初始化
if (this.bindingResult == null) {
initBeanPropertyAccess();
}
return this.bindingResult;
}
同樣是lazy-init,當第一次調用時 ,調用initBeanPropertyAccess 進行初始化. initBeanPropertyAccess 代碼如下:

public void initBeanPropertyAccess() {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
this.bindingResult = createBeanPropertyBindingResult();
}
調用

protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
// 1. 實例化BeanPropertyBindingResult
BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),
getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
if (this.conversionService != null) {
// 2. 這個步驟是一定會執行的, 進行初始化
result.initConversion(this.conversionService);
}
if (this.messageCodesResolver != null) {
// 3. 設置messageCodesResolver
result.setMessageCodesResolver(this.messageCodesResolver);
}
return result;
}
3件事:

實例化BeanPropertyBindingResult
這個步驟是一定會執行的, 進行初始化conversionService
設置messageCodesResolver
調用BeanPropertyBindingResult#getPropertyAccessor.

public final ConfigurablePropertyAccessor getPropertyAccessor() {
// 1. lazy-inits
if (this.beanWrapper == null) {
// 1.1 最終調用PropertyAccessorFactory#forBeanPropertyAccess,直接實例化了BeanWrapperImpl
this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true);
this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
}
return this.beanWrapper;
}
還是同樣的味道,lazy-init,最終調用PropertyAccessorFactory#forBeanPropertyAccess,直接實例化了BeanWrapperImpl.

調用BeanPropertyBindingResult#setPropertyValues 進行注入.
如果在注入過程出現異常,則記錄異常.

其中 1.3 BeanPropertyBindingResult#setPropertyValues ,代碼如下:

public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
throws BeansException {

List<PropertyAccessException> propertyAccessExceptions = null;
// 1. 獲得propertyValues,一般情況下,此時傳入的是MutablePropertyValues,因此直接通過MutablePropertyValues#getPropertyValueList 獲取即可
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
// 2. 遍歷propertyValues,依次調用setPropertyValue 進行處理
for (PropertyValue pv : propertyValues) {

// 刪除一些無用的try-cath,減少篇幅...
setPropertyValue(pv);

}

if (propertyAccessExceptions != null) {
PropertyAccessException[] paeArray =
propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]);
throw new PropertyBatchUpdateException(paeArray);
}
}
3件事:

獲得propertyValues,一般情況下,此時傳入的是MutablePropertyValues,因此直接通過MutablePropertyValues#getPropertyValueList 獲取即可
遍歷propertyValues,依次調用setPropertyValue 進行處理
如果propertyAccessExceptions != null,則意味在第2步處理中,出現了問題,則拋出PropertyBatchUpdateException.
其中第2步,最終調用的是AbstractNestablePropertyAccessor#setPropertyValue,代碼如下:

public void setPropertyValue(PropertyValue pv) throws BeansException {
PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
if (tokens == null) {
String propertyName = pv.getName();
AbstractNestablePropertyAccessor nestedPa;
try {
nestedPa = getPropertyAccessorForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "' does not exist", ex);
}
tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
if (nestedPa == this) {
pv.getOriginalPropertyValue().resolvedTokens = tokens;
}
nestedPa.setPropertyValue(tokens, pv);
}
else {
setPropertyValue(tokens, pv);
}
}
調用getPropertyAccessorForPropertyPath,處理對象導航,還是由於此處分析的最簡單的場景,因此這里返回的就是當前類.
調用getPropertyNameTokens,這里處理的是集合的情況.同樣,這里先不進行分析
調用AbstractNestablePropertyAccessor#setPropertyValue,進行賦值.代碼如下:

protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
if (tokens.keys != null) {
processKeyedProperty(tokens, pv);
}
else {
processLocalProperty(tokens, pv);
}
}
2件事:

如果 PropertyTokenHolder 中的keys 不等於null,則意味着是要對集合進行賦值,為什么?這個我們后面有解釋.
否則調用 processLocalProperty進行處理.因為我們這里分析的是最簡單的情況,因此會在這里進行處理.代碼如下:
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
// 1. 獲得PropertyHandler
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
// 2. 如果ph 等於null,或者 PropertyHandler 沒有set方法
if (ph == null || !ph.isWritable()) {
// 2.1 如果該屬性是可選的,則打印日志,否則拋出異常
if (pv.isOptional()) {

return;
}
else {
throw createNotWritablePropertyException(tokens.canonicalName);
}
}
// 3. 進行轉換處理
Object oldValue = null;
Object originalValue = pv.getValue();
Object valueToApply = originalValue;
if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
if (pv.isConverted()) {
valueToApply = pv.getConvertedValue();
}
else {
if (isExtractOldValueForEditor() && ph.isReadable()) {

oldValue = ph.getValue();

}
valueToApply = convertForProperty(
tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
}
pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
}
// 4. 進行賦值
ph.setValue(this.wrappedObject, valueToApply);
}
}
4件事

獲得PropertyHandler,注意,這里調用的是BeanWrapperImpl.getLocalPropertyHandler代碼如下:

protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
if (pd != null) {
return new BeanPropertyHandler(pd);
}
return null;
}
調用getCachedIntrospectionResults 獲得CachedIntrospectionResults.代碼如下:

private CachedIntrospectionResults getCachedIntrospectionResults() {
Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance");
if (this.cachedIntrospectionResults == null) {
// lazy-init,第一次調用時初始化
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
}
return this.cachedIntrospectionResults;
}
同樣的調調,lazy-init,調用CachedIntrospectionResults#forClass獲得CachedIntrospectionResults. 注意,這里傳入的是target,也就是 People.class.代碼如下:


static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
// 1. 嘗試從strongClassCache,softClassCache中獲取,如果不為空,則直接返回.
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
// 2. 初始化
results = new CachedIntrospectionResults(beanClass);
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
// 3. 如果當前給定的類是否是給定的ClassLoader 或者是其父ClassLoader 加載的 或者 判斷給定的classLoader 是否是acceptedClassLoaders的子classLoader
// 一般都是這個了,那么就使用strongClassCache,否則使用softClassCache
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
isClassLoaderAccepted(beanClass.getClassLoader())) {
classCacheToUse = strongClassCache;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
}
classCacheToUse = softClassCache;
}
// 4. 加入緩存中
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}

4件事:

嘗試從strongClassCache,softClassCache中獲取,如果不為空,則直接返回.
否則,進行初始化.CachedIntrospectionResults.其中,有如下代碼:

beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
Introspector.getBeanInfo(beanClass));
這里調用了java.beans.Introspector 獲取BeanInfo,而CachedIntrospectionResults只是對BeanInfo包裝而已,關於CachedIntrospectionResults的初始化,這里就不繼續深入了,也沒有必要.

如果當前給定的類是否是給定的ClassLoader 或者是其父ClassLoader 加載的 或者 判斷給定的classLoader 是否是acceptedClassLoaders的子classLoader,那么就使用strongClassCache,否則使用softClassCache.一般就是strongClassCache
加入緩存中
調用CachedIntrospectionResults#getPropertyDescriptor 獲得PropertyDescriptor.注意,這里返回的是java.beans.PropertyDescriptor.是關於java反射的.不懂的可以百度一下.
如果PropertyDescriptor 不等於null,就意味着在target 也就是 People 中找到了對應的屬性.因此,直接返回BeanPropertyHandler.
否則,返回null.
如果ph 等於null,或者 PropertyHandler set方法不存在或者不是public的,如果該屬性是可選的,則打印日志,否則拋出異常.
調用convertForProperty,進行屬性的轉換,這里就是真正屬性轉換的地方,同樣,后面有解釋
調用BeanPropertyHandler#setValue進行賦值.代碼如下:

public void setValue(final Object object, Object valueToApply) throws Exception {
// 1. 獲得該屬性對應的set方法
final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
this.pd.getWriteMethod());
// 2. 如果該方法為私有的,則通過反射的方式,設置為可訪問的
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
writeMethod.setAccessible(true);
return null;
}
});
}
else {
writeMethod.setAccessible(true);
}
}
// 3. 進行賦值
final Object value = valueToApply;
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
writeMethod.invoke(object, value);
return null;
}
}, acc);
}
catch (PrivilegedActionException ex) {
throw ex.getException();
}
}
else {
writeMethod.invoke(getWrappedInstance(), value);
}
}
}
3件事:

獲得該屬性對應的set方法
如果該方法為私有的,則通過反射的方式,設置為可訪問的
通過writeMethod#invoke的方式調用set 方法 進行賦值.
至此,關於簡單屬性的注入(String類型)就分析完了,接下來就占位符,集合,對象導航,屬性轉換來分別做處理.

占位符處理
在之前的分析過程中,我們跳過了占位符處理工程的分析,這里我們將配置文件改為如下:

com.example.demo.name=${aaa:hi}
1
還是分析對People name 屬性的注入. 之前的步驟同之前的分析的一樣.只不過在PropertyPlaceholderHelper# parseStringValue處開始了對占位符的處理.代碼如下:

protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

StringBuilder result = new StringBuilder(value);

// 1. 通過String#indexOf 獲取前綴(一般都是${)的下標
int startIndex = value.indexOf(this.placeholderPrefix);
// 2 如果存在
while (startIndex != -1) {
// 2.1 獲得后綴,此時獲得是最小的后綴,嵌套處理
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {// 3 如果endIndex 存在,
// 3.1 通過字符串的截取獲得占位符,如${placeholder},那么此時獲得的是placeholder
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
// 3.2 進行循環引用的檢查,如果存在,則拋出IllegalArgumentException
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
// 3.3 遞歸處理
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
// 3.4 進行解析占位符
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 3.5 如果propVal 不等於null並且 valueSeparator(默認為 :)不等於null,則此時意味有默認值,
// 那么此時調用placeholderResolver#resolvePlaceholder 進行解析,如果解析失敗的話,則返回默認值
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;
}
}
}

// 3.6
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
// 3.6.1 如果propVal 不等於null,則意味着解析成功,則繼續遞歸處理,處理完后,進行替換,

propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
// 進行替換
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
// 重新計算startIndex
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
// 3.6.2 如果沒有解析成功並且ignoreUnresolvablePlaceholders,則重新計算startIndex
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
// 3.6.3 拋出IllegalArgumentException
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
// 3.7 從visitedPlaceholders 刪除.該算法有點類似dfs.
visitedPlaceholders.remove(originalPlaceholder);
}
else {
// 2.2 將startIndex 設為-1,則意味着已經處理完了
startIndex = -1;
}
}
return result.toString();
}
首先通過String#indexOf 獲得 { 的下標,這里是存在的,因此繼續處理。此時調用的是PropertyPlaceholderHelper#findPlaceholderEndIndex 獲的后綴.比方說如果我們配置的是  
 ${aa} 那么此時返回的就是}的下標,如果配置的是{ 的下標,這里是存在的,因此繼續處理。此時調用的是PropertyPlaceholderHelper#findPlaceholderEndIndex 獲的后綴.比方說如果我們配置的是   ${aa} 那么此時返回的就是}的下標,如果配置的是{aa}${bb}我們返回的就是,a后面的}的下標.代碼如下:

private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderSuffix.length();
}
else {
return index;
}
}
else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
withinNestedPlaceholder++;
index = index + this.simplePrefix.length();
}
else {
index++;
}
}
return -1;
}
接下來,進行字符截取,此時我們配置的是com.example.demo.name=${aaa:hi},截取后獲得的是aaa:hi,

第三步,將aaa:hi 作為參數,遞歸調用parseStringValue,由於此時aaa:hi 不存在${,因此直接返回的還是aaa:hi.

接下來,判斷是否存在:,對於當前,是存在的,因此對其進行截取分別獲得actualPlaceholder,defaultValue.對於當前, actualPlaceholder = aaa, defaultValue = hi, 然后調用PlaceholderResolver#resolvePlaceholder獲得值,如果actualPlaceholder 解析失敗,則將propVal 設為默認值.關於這部分對於的源碼如下:

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;
}
}
}
此刻,調用PlaceholderResolver#resolvePlaceholder,實際上調用的是在AbstractPropertyResolver#doResolvePlaceholders中實例化的PlaceholderResolver的實現.代碼如下:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
return getPropertyAsRawString(placeholderName);
}
});
}
因此這里最終調用PropertySourcesPropertyResolver#getPropertyAsRawString,代碼如下:

protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
調用了
org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(String, Class, boolean)方法,接下來的故事,就很之前一樣了,這里就不在贅述了.

集合處理
接下來我們將application.properties 改為

com.example.demo.address[0]=北京
com.example.demo.address[1]=上海
com.example.demo.address[2]=廣州
這里分析對People 中 address 屬性的注入. 同樣,前面的准備工作都一樣,在對屬性進行注入時,會調用AbstractNestablePropertyAccessor#setPropertyValue,代碼如下:

public void setPropertyValue(String propertyName, Object value) throws BeansException {
AbstractNestablePropertyAccessor nestedPa;
try {
// 1. 生成AbstractNestablePropertyAccessor
nestedPa = getPropertyAccessorForPropertyPath(propertyName);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
"Nested property in path '" + propertyName + "' does not exist", ex);
}
// 2. 獲得PropertyTokenHolder, getFinalPath 獲得最終的PropertyName
PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
// 3. 進行賦值
nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}
這里調用了AbstractNestablePropertyAccessor#getPropertyNameTokens,代碼如下:

private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
PropertyTokenHolder tokens = new PropertyTokenHolder();
String actualName = null;
List<String> keys = new ArrayList<String>(2);
int searchIndex = 0;
while (searchIndex != -1) {
// 1. 獲得 [ 的下標
int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
searchIndex = -1;
if (keyStart != -1) {
// 2 如果存在的話,則截取獲得]的下標
int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
if (keyEnd != -1) {
// 3. 如果存在的話,則截取出actualName,例如[map],那么此時就是""
if (actualName == null) {
actualName = propertyName.substring(0, keyStart);
}
// 4. 截取出key 此時就是map
String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
if (key.length() > 1 && (key.startsWith("'") && key.endsWith("'")) ||
(key.startsWith("\"") && key.endsWith("\""))) {
key = key.substring(1, key.length() - 1);
}
keys.add(key);
searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
}
}
}
tokens.actualName = (actualName != null ? actualName : propertyName);
tokens.canonicalName = tokens.actualName;
if (!keys.isEmpty()) {
// [ + StringUtils#collectionToDelimitedString(keys,][)+]
tokens.canonicalName += PROPERTY_KEY_PREFIX +
StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
PROPERTY_KEY_SUFFIX;
tokens.keys = StringUtils.toStringArray(keys);
}
return tokens;
}
步驟如下:

首先獲得[ 的下標

如果存在的話,則嘗試獲取]的下標.

如果存在的]下標的話, 則截取出屬性值,對應於當前的情況,就是address.然后加入keys中.
如果不存在,則意味着不存在集合的情況.接下來的處理就很之前一樣.
注意在這里, keys 只加入了一個, 如:0,為什么呢?

因為我們是循環處理的,這點很重要.后面的步驟都是在此基礎上進行的,在處理完com.example.demo.address[0]=北京 后,再處理 com.example.demo.address[1]=上海 .為啥呢? 因為在AbstractPropertyAccessor#setPropertyValues中我們是通過遍歷的方式處理的,代碼如下:

for (PropertyValue pv : propertyValues) {

setPropertyValue(pv);

}
接下來, 在AbstractNestablePropertyAccessor#setPropertyValue,由於此刻keys 不等於null,因此會執行processKeyedProperty.代碼如下:

protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
if (tokens.keys != null) {
processKeyedProperty(tokens, pv);
}
else {
processLocalProperty(tokens, pv);
}
}
processKeyedProperty 代碼如下:

private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
// 1. 獲得
Object propValue = getPropertyHoldingValue(tokens);
String lastKey = tokens.keys[tokens.keys.length - 1];

if (propValue.getClass().isArray()) {
// 省略....
}

else if (propValue instanceof List) {
PropertyHandler ph = getPropertyHandler(tokens.actualName);
Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
List<Object> list = (List<Object>) propValue;
int index = Integer.parseInt(lastKey);
Object oldValue = null;
if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index);
}
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType, ph.nested(tokens.keys.length));
int size = list.size();
if (index >= size && index < this.autoGrowCollectionLimit) {
for (int i = size; i < index; i++) {
try {
list.add(null);
}
catch (NullPointerException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Cannot set element with index " + index + " in List of size " +
size + ", accessed using property path '" + tokens.canonicalName +
"': List does not support filling up gaps with null elements");
}
}
list.add(convertedValue);
}
else {
try {
list.set(index, convertedValue);
}
catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Invalid list index in property path '" + tokens.canonicalName + "'", ex);
}
}
}

else if (propValue instanceof Map) {
// 省略....
}

else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Property referenced in indexed property path '" + tokens.canonicalName +
"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
}
2件事:

調用getPropertyHoldingValue 獲得 屬性對應的對象. 對於當前,就是獲得People 中address 所對應的對象實例.代碼如下:

private Object getPropertyHoldingValue(PropertyTokenHolder tokens) {
// Apply indexes and map keys: fetch value for all keys but the last one.
// 1. 實例化PropertyTokenHolder
PropertyTokenHolder getterTokens = new PropertyTokenHolder();
getterTokens.canonicalName = tokens.canonicalName;
getterTokens.actualName = tokens.actualName;
getterTokens.keys = new String[tokens.keys.length - 1];
System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);

Object propValue;
try {
// 2. 獲得值
propValue = getPropertyValue(getterTokens);
}
catch (NotReadablePropertyException ex) {
throw new NotWritablePropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Cannot access indexed value in property referenced " +
"in indexed property path '" + tokens.canonicalName + "'", ex);
}

if (propValue == null) {
// null map value case
if (isAutoGrowNestedPaths()) {
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
propValue = setDefaultValue(getterTokens);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Cannot access indexed value in property referenced " +
"in indexed property path '" + tokens.canonicalName + "': returned null");
}
}
return propValue;
}
實例化PropertyTokenHolder
調用getPropertyValue,獲得該屬性所對應的對象–> propValue.代碼如下:

protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
String propertyName = tokens.canonicalName;
String actualName = tokens.actualName;
// 1. 根據屬性值獲得PropertyHandler
PropertyHandler ph = getLocalPropertyHandler(actualName);
// 2. 如果PropertyHandler等於null或者沒有get方法,拋出異常
if (ph == null || !ph.isReadable()) {
throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
}
// 3. 獲得對應的屬性值.
Object value = ph.getValue();
if (tokens.keys != null) {
// 4. 如果tokens.keys 不等於null,這里是不會執行的
if (value == null) {
// 4.1 如果autoGrowNestedPaths 值為true,則生成默認值,一般都會生成默認值的
if (isAutoGrowNestedPaths()) {
value = setDefaultValue(tokens.actualName);
}
else {
// 4.2 拋出異常
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
}
}
String indexedPropertyName = tokens.actualName;
// apply indexes and map keys
// 5. 依次進行遍歷
for (int i = 0; i < tokens.keys.length; i++) {
String key = tokens.keys[i];
// 5.1 如果value等於null,則拋出異常
if (value == null) {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
"Cannot access indexed value of property referenced in indexed " +
"property path '" + propertyName + "': returned null");
}
else if (value.getClass().isArray()) {
// 省略...
}
else if (value instanceof List) {
int index = Integer.parseInt(key);
List<Object> list = (List<Object>) value;
growCollectionIfNecessary(list, index, indexedPropertyName, ph, i + 1);
value = list.get(index);
}
else if (value instanceof Set) {
// 省略...
}
else if (value instanceof Map) {
// 省略...
}
else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Property referenced in indexed property path '" + propertyName +
"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
}
indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX;
}
}
return value;
}
獲得調用getLocalPropertyHandler獲得PropertyHandler,這個我們前面已經分析過了.
如果PropertyHandler等於null或者沒有get方法,拋出NotReadablePropertyException
獲得對應的屬性對象,也就是People 中的address.
如果tokens.keys 不等於null,對於當前來說,keys 不等於null,因此是會執行的.

如果autoGrowNestedPaths 值為true,則生成默認值,一般都會生成默認值的,否則拋出NullValueInNestedPathException.
依次進行遍歷keys,針對value的不同類型做不同的處理,這里我們只看List,處理如下:
將key 轉為index,注意這里傳入的是0.
通過list.get(index)的方式獲得下標所對應的對象.
如果propValue 等於null,如果autoGrowNestedPaths 屬性值為true,則調用setDefaultValue 進行實例化,否則拋出NullValueInNestedPathException異常,一般情況下, autoGrowNestedPaths為true.同樣,該方法一般情況下都會執行的.代碼如下:

private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) {
TypeDescriptor desc = getPropertyTypeDescriptor(tokens.canonicalName);
Class<?> type = desc.getType();
if (type == null) {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName,
"Could not determine property type for auto-growing a default value");
}
Object defaultValue = newValue(type, desc, tokens.canonicalName);
return new PropertyValue(tokens.canonicalName, defaultValue);
}

這樣就實例化了,具體是怎么實例化的,這里就不展開了.

針對propValue的類型做不同的處理,如果該類型不是數字,List,Map,則拋出InvalidPropertyException.這里我們只分析list的情況,其他類似.

獲得屬性對應的對象
獲得集合的泛型
獲得下標
進行轉換
如果下標大於集合的size,則將index - size 這段范圍內,插入null值,最后在插入對應的值.否則,直接插入即可.
至此,集合的注入就分析完了.

對象導航處理
我們將配置文件改為如下:

com.example.demo.phone.number=1111111
1
這里分析對People 中 phone 的number 屬性的注入. 還是同樣的套路,前面的准備工作都一樣,最終在進行屬性注入時,調用了AbstractNestablePropertyAccessor#setPropertyValue.在該方法中調用了getPropertyAccessorForPropertyPath 用於處理嵌套屬性.代碼如下:

protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
// 1. 通過PropertyAccessorUtils#getFirstNestedPropertySeparatorIndex 獲得下標
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
// Handle nested properties recursively.
if (pos > -1) {
// 如果存在的話,則意味着有嵌套存在,則遞歸處理,例如 map[my.key],
String nestedProperty = propertyPath.substring(0, pos);// nestedProperty = map[my
String nestedPath = propertyPath.substring(pos + 1); // nestedPath = key
// 3. 獲得嵌套對象
AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
// 4. 獲取AbstractNestablePropertyAccessor,遞歸調用
return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
}
else {
// 如果不存在,則返回this
return this;
}
}
2件事:

通過PropertyAccessorUtils#getFirstNestedPropertySeparatorIndex 獲得下標.該方法最終調用了PropertyAccessorUtils#getNestedPropertySeparatorIndex,該方法處理的邏輯很簡單,看是否存在.,代碼如下:

private static int getNestedPropertySeparatorIndex(String propertyPath, boolean last) {
boolean inKey = false;
int length = propertyPath.length();
int i = (last ? length - 1 : 0);// 起始下標
while (last ? i >= 0 : i < length) {
switch (propertyPath.charAt(i)) {
case PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR: // [
case PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR:// ]
inKey = !inKey;
break;
case PropertyAccessor.NESTED_PROPERTY_SEPARATOR_CHAR: // .
if (!inKey) {
return i;
}
}
if (last) {
i--;
}
else {
i++;
}
}
return -1;
}
如果存在嵌套屬性,則遞歸處理

通過字符串截取,獲得nestedProperty,nestedPath .對於當前來說, nestedProperty = phone, nestedPath = number
調用getNestedPropertyAccessor 獲得AbstractNestablePropertyAccessor.代碼如下:

private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
// 1. 如果nestedPropertyAccessors 等於null,則實例化
if (this.nestedPropertyAccessors == null) {
this.nestedPropertyAccessors = new HashMap<String, AbstractNestablePropertyAccessor>();
}
// Get value of bean property.
// 2. 獲取屬性名
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
String canonicalName = tokens.canonicalName;
// 3. 獲得對應的值
Object value = getPropertyValue(tokens);
if (value == null || (value.getClass() == javaUtilOptionalClass && OptionalUnwrapper.isEmpty(value))) {
if (isAutoGrowNestedPaths()) {
value = setDefaultValue(tokens);
}
else {
throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
}
}

// Lookup cached sub-PropertyAccessor, create new one if not found.
// 4. 獲得訪問嵌套對象
AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName);
if (nestedPa == null || nestedPa.getWrappedInstance() !=
(value.getClass() == javaUtilOptionalClass ? OptionalUnwrapper.unwrap(value) : value)) {
if (logger.isTraceEnabled()) {
logger.trace("Creating new nested " + getClass().getSimpleName() + " for property '" + canonicalName + "'");
}
// 5. 如果不存在則創建一個,實例化的是BeanWrapperImpl
nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);
// Inherit all type-specific PropertyEditors.
copyDefaultEditorsTo(nestedPa);
copyCustomEditorsTo(nestedPa, canonicalName);
// 6. 存入緩存
this.nestedPropertyAccessors.put(canonicalName, nestedPa);
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Using cached nested property accessor for property '" + canonicalName + "'");
}
}
return nestedPa;
}
6件事:

如果nestedPropertyAccessors 等於null,則實例化. lazy-init
調用getPropertyNameTokens 獲得PropertyTokenHolder,對於當前,獲得的是phone所對應的PropertyTokenHolder.這個方法,我們之前已經分析過了。
調用getPropertyValue , 獲得phone所對應的對象。關於這個方法,我們也已經分析過了,此時會將people中的phone 實例化.
嘗試從nestedPropertyAccessors緩存中獲得AbstractNestablePropertyAccessor. 如果沒有獲得,則實例化一個BeanPropertyHandler.然后進行初始化后放入nestedPropertyAccessors.
遞歸調用getPropertyAccessorForPropertyPath.
那我們的例子來說,第一次傳入的參數是phone.number,有嵌套屬性,因此會在實例化phone所對應后的AbstractNestablePropertyAccessor后,會遞歸調用getPropertyAccessorForPropertyPath,此時由於傳入的參數是number,因此方法退出,因此該遞歸最終返回的是 phone所對應后的AbstractNestablePropertyAccessor.

接着,AbstractNestablePropertyAccessor#getFinalPath,獲得最終的路徑,代碼如下:

protected String getFinalPath(AbstractNestablePropertyAccessor pa, String nestedPath) {
if (pa == this) {
return nestedPath;
}
return nestedPath.substring(PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(nestedPath) + 1);
}
由於pa 不等於this,因此會調用PropertyAccessorUtils#getLastNestedPropertySeparatorIndex 方法獲得最后一個. 所對應的下標,通過字符串截取后,獲得屬性名,此時,會獲得number。

2個問題:

為什么pa 不等於 this?

還是拿例子來說話,this, 指的是people 所對應的AbstractNestablePropertyAccessor,pa 在當前來說,是phone所對應的AbstractNestablePropertyAccessor.明顯不相等的.

為什么只需返回最后一個屬性,就行了? 也就是

假如我們新增如下一個類型:

public class Operator {// 運營商

private String name;

// get set 忽略,自己加上即可..
}
然后將Phone 改為如下:

public class Phone {

private String number;
private Operator operator;

// get set 忽略,自己加上即可..
}

將配置文件加入如下配置:

com.example.demo.phone.operator.name=移動
1
為什么此時返回是name?

理由很簡單,因為在調用AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath時是遞歸處理的,該方法會首先實例化People 中的phone,接着實例化Phone 中operator所對應的Operator對象.后續的故事,就是直接賦值了,我們已經分析過了.

屬性轉換處理
這里,我們來看最后一個–>屬性轉換,將配置文件該為如下:

com.example.demo.age=11
1
之前的准備工作,就不在贅述了,在最終進行賦值時,會調用
AbstractNestablePropertyAccessor#processLocalProperty,而在該方法中的第三步,會調用AbstractNestablePropertyAccessor#convertForProperty進行轉換處理,代碼如下:

protected Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td)
throws TypeMismatchException {

return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}

最終調用TypeConverterDelegate#convertIfNecessary,代碼如下:

1
獲得自定義的PropertyEditor
從propertyEditorRegistry 獲得自定義的ConversionService,這里使用的是org.springframework.boot.bind.RelaxedConversionService
如果PropertyEditor 等於null && conversionService 不等於null,&& newValue 不等於null,&& typeDescriptor 不等於null,則調用ConversionService#convert.

這里由於不存在自定義的PropertyEditor,同時第2步獲得的propertyEditorRegistry不等於null,因此最終會調用RelaxedConversionService#convert 進行轉換,代碼如下:

public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (this.conversionService != null) {
try {
return this.conversionService.convert(source, sourceType, targetType);
}
catch (ConversionFailedException ex) {
// Ignore and try the additional converters
}
}
return this.additionalConverters.convert(source, sourceType, targetType);
}
2件事:

如果conversionService 不等於null,則調用conversionService#convert 進行轉換.對於當前,會執行這里, conversionService為GenericConversionService,代碼如下:

public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
// 1. 如果sourceType 等於null,則拋出ConversionFailedException
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
// 2. 如果source不等於null,並且sourceType 不是source 的類型,則拋出IllegalArgumentException
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
// 3. 獲得GenericConverter
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
// 3.1 如果Converter,則通過ConversionUtils#invokeConverter 進行轉換
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
// 4. 當Converter 沒有找到時 ,進行處理
return handleConverterNotFound(source, sourceType, targetType);
}

4件事:

如果sourceType 等於null,則拋出ConversionFailedException
如果source不等於null,並且sourceType 不是source 的類型,則拋出IllegalArgumentException
獲得GenericConverter,如果Converter 不等於null,則通過ConversionUtils#invokeConverter 進行轉換.代碼如下:

protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
// 1. 實例化ConverterCacheKey
ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
// 2. 嘗試從converterCache 獲取
GenericConverter converter = this.converterCache.get(key);
if (converter != null) {
return (converter != NO_MATCH ? converter : null);
}

// 3. 從converters 獲取
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
// 4. 如果還沒有得到,則返回默認的Converter
converter = getDefaultConverter(sourceType, targetType);
}

if (converter != null) {
// 5. 如果不等於null,則放入緩存中
this.converterCache.put(key, converter);
return converter;
}

// 6. 如果converter 等於null,則在converterCache中放入NO_MATCH
this.converterCache.put(key, NO_MATCH);
return null;
}
6件事:

實例化ConverterCacheKey
嘗試從converterCache 獲取
從converters 獲取
如果還沒有得到,則返回默認的Converter
如果不等於null,則放入緩存中
如果converter 等於null,則在converterCache中放入NO_MATCH
對於當前,獲得的是ConverterFactoryAdapter,其convert方法如下:

public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return convertNullSource(sourceType, targetType);
}
return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}
最終調用的是StringToNumber#convert 方法,代碼如下:

public T convert(String source) {
if (source.isEmpty()) {
return null;
}
return NumberUtils.parseNumber(source, this.targetType);
}
至此,就將com.example.demo.age = 11 ,由原先的字符串,轉換為了Integer.后面只需賦值即可了,關於這個,我們已經分析過了.

當Converter 沒有找到時 ,進行處理
否則調用additionalConverters#convert 進行轉換。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM