文章转自 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 进行转换。

@ConfigurationProperties 配置详解的更多相关文章

  1. springboot配置详解

    springboot配置详解 Author:SimpleWu properteis文件属性参考大全 springboot默认加载配置 SpringBoot使用两种全局的配置文件,全局配置文件可以对一些 ...

  2. Log4j配置详解(转)

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

  3. logback 常用配置详解<appender>

    logback 常用配置详解 <appender> <appender>: <appender>是<configuration>的子节点,是负责写日志的 ...

  4. [转]阿里巴巴数据库连接池 druid配置详解

    一.背景 java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池.数据库连接池有很多选择,c3p.dhcp.proxool等,druid作为一名后起之秀,凭借其出色 ...

  5. libCURL开源库在VS2010环境下编译安装,配置详解

    libCURL开源库在VS2010环境下编译安装,配置详解 转自:http://my.oschina.net/u/1420791/blog/198247 http://blog.csdn.net/su ...

  6. logback配置详解3<filter>

    logback 常用配置详解(三) <filter> <filter>: 过滤器,执行一个过滤器会有返回个枚举值,即DENY,NEUTRAL,ACCEPT其中之一.返回DENY ...

  7. logback配置详解2<appender>

    logback 常用配置详解(二) <appender> <appender>: <appender>是<configuration>的子节点,是负责写 ...

  8. log4j.properties配置详解

    1.Loggers Loggers组件在此系统中被分为五个级别:DEBUG.INFO.WARN.ERROR和FATAL.这五个级别是有顺序的,DEBUG < INFO < WARN < ...

  9. Log4J日志配置详解

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

随机推荐

  1. @RequestMapping 详解

    RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上.用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径.RequestMapping注解有六个属性,下面我们把她分 ...

  2. 2018-08-11 中文代码示例之Spring Boot 2.0.3问好

    上次试用Spring Boot还是两年前: 中文代码示例之Spring Boot 1.3.3演示. 打算用在一个讨论组内小项目上, 于是从官网Building an Application with ...

  3. Human Motion Analysis with Wearable Inertial Sensors——阅读1

    Human Motion Analysis with Wearable Inertial Sensors——阅读 博主认为对于做室内定位和导航的人这是一篇很很棒的文章,不是他的技术很牛,而是这是一篇医 ...

  4. JMeter 关于JMeter 正则表达式提取器的一点研究

    关于JMeter 正则表达式提取器的一点研究   by:授客 QQ:1033553122 1.   实验环境: JMeter 2.13 2.   添加正则表达式提取器 右键线程组->添加-> ...

  5. Android为TV端助力 比较完善json请求格式

    public static String getHttpText(String url) { if (MyApplication.FOR_DEBUG) { Log.i(TAG, "[getH ...

  6. Android Studio调试时遇见Install Repository and sync project的问题

    我们可以看到,报的错是“Failed to resolve: com.android.support:appcompat-v7:16.+”,也就是我们在build.gradle中最后一段中的compi ...

  7. button改变某div内文字内容的显示

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 机器学习之EM算法(五)

    摘要 EM算法全称为Expectation Maximization Algorithm,既最大期望算法.它是一种迭代的算法,用于含有隐变量的概率参数模型的最大似然估计和极大后验概率估计.EM算法经常 ...

  9. OpenCvSharp尝试

    OpenCvSharp是封装了OpenCV的.net版本 项目地址:https://github.com/shimat/opencvsharp 简单使用: 1.NuGet安装 2.使用OpenCvSh ...

  10. 远程桌面连接一台关联无线的电脑(A)时,A电脑无线总是断开导致远程桌面连接失败

    1. 我的环境: 两台电脑,分别记为PC1和PC2,PC1有线或者无线连在路由器上,PC2无线连在同一个路由器上.(当然,我的PC1是win10系统,PC2是win7系统) 2.  PC1只要一远程连 ...