前言
之前我们分析了SpringApplication#run方法的前3步.在这里我们分析第4步–>创建一个DefaultApplicationArguments对象,调用prepareEnvironment方法.
StandardServletEnvironment-type-tree.png
分析
创建DefaultApplicationArguments.将启动时的参数传入到其构造器中.其构造器如下:
public DefaultApplicationArguments(String[] args) { Assert.notNull(args, "Args must not be null"); this.source = new Source(args); this.args = args; }
在构造器中初始化了Source.其继承结构如下:
在Source的构造器中调用了父类的SimpleCommandLinePropertySource的构造器.如下:
public SimpleCommandLinePropertySource(String... args) { super(new SimpleCommandLineArgsParser().parse(args)); }
实例化了SimpleCommandLineArgsParser并调用其parse方法进行解析参数.代码如下:
public CommandLineArgs parse(String... args) { CommandLineArgs commandLineArgs = new CommandLineArgs(); for (String arg : args) { if (arg.startsWith("--")) { String optionText = arg.substring(2, arg.length()); String optionName; String optionValue = null; if (optionText.contains("=")) { optionName = optionText.substring(0, optionText.indexOf("=")); optionValue = optionText.substring(optionText.indexOf("=")+1, optionText.length()); } else { optionName = optionText; } if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) { throw new IllegalArgumentException("Invalid argument syntax: " + arg); } commandLineArgs.addOptionArg(optionName, optionValue); } else { commandLineArgs.addNonOptionArg(arg); } } return commandLineArgs; }
逻辑很简单,首先初始化了CommandLineArgs.然后遍历args.如果args是–开头的,就加入OptionArg中,否则加入到NonOptionArg中.
解析完毕后,接着调用CommandLinePropertySource的构造器,代码如下:
public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; public CommandLinePropertySource(T source) { super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source); }
EnumerablePropertySource的构造器如下:
public EnumerablePropertySource(String name, T source) { super(name, source); }
PropertySource 构造器如下:
public PropertySource(String name, T source) { Assert.hasText(name, "Property source name must contain at least one character"); Assert.notNull(source, "Property source must not be null"); this.name = name; this.source = source; }
至此DefaultApplicationArguments初始化完毕.
DefaultApplicationArguments初始化完毕后,调用SpringApplication#prepareEnvironment.代码如下:
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 1. Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 2. 配置环境的信息 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 3. 通知所有的观察者,环境已经准备好了 listeners.environmentPrepared(environment); if (!this.webEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } return environment; }
代码做了3件事:
- 获取或者创建ConfigurableEnvironment
- 配置ConfigurableEnvironment
- 通知所有的观察者,发送ApplicationEnvironmentPreparedEvent事件.
获取或者创建ConfigurableEnvironment调用的是getOrCreateEnvironment方法.代码如下:
private ConfigurableEnvironment getOrCreateEnvironment() { // 1. 如果environment不为空则直接返回 if (this.environment != null) { return this.environment; } // 2. 如果是web环境则直接实例化StandardServletEnvironment类 if (this.webEnvironment) { return new StandardServletEnvironment(); } // 3. 如果不是web环境则直接实例化StandardEnvironment类 return new StandardEnvironment(); }
首先判断environment是否为空,如果不为空直接返回,否则 如果是web环境则直接实例化StandardServletEnvironment,否则返回StandardEnvironment.
一般创建的是StandardServletEnvironment.
StandardServletEnvironment类的继承结构如下:
在StandardServletEnvironment实例化时,会触发AbstractEnvironment实例化.而在AbstractEnvironment的构造器中会调用customizePropertySources方法.代码如下:
public AbstractEnvironment() { customizePropertySources(this.propertySources); if (logger.isDebugEnabled()) { logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources); } }
而在StandardServletEnvironment中的customizePropertySources方法如下:
/** Servlet context init parameters property source name: {@value} */ public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"; /** Servlet config init parameters property source name: {@value} */ public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"; /** JNDI property source name: {@value} */ public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties"; protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources); }
在该方法中添加了servletConfigInitParams,servletContextInitParams,jndiProperties对应的Source.
在笔者的demo环境中, jndiProperties没有添加进去.然后调用其StandardEnvironment#customizePropertySources方法.添加MapPropertySource,SystemEnvironmentPropertySource.代码如下:
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
配置ConfigurableEnvironment,调用的是SpringApplication#configureEnvironment方法.代码如下:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { configurePropertySources(environment, args); configureProfiles(environment, args); }
做了2件事:
- 配置PropertySources
- 配置Profiles
configurePropertySources方法如下:
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); // 1. 如果defaultProperties不为空,则继续添加defaultProperties if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast( new MapPropertySource("defaultProperties", this.defaultProperties)); } // 2. 如果addCommandLineProperties为true并且有命令参数, // 分两步骤走:第一步存在commandLineArgs则继续设置属性;第二步commandLineArgs不存在则在头部添加commandLineArgs if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(new SimpleCommandLinePropertySource( name + "-" + args.hashCode(), args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } // MutablePropertySources类中propertySourceList已经存在的属性为 }
做了2件事:
- 如果defaultProperties不为空,则继续添加defaultProperties
在当前环境下defaultProperties没有添加进去.
- 如果addCommandLineProperties为true并且有命令参数,
分两步骤走:第一步存在commandLineArgs则继续设置属性;第二步commandLineArgs不存在则在头部添加commandLineArgs
那么该方法执行完毕后,MutablePropertySources类中propertySourceList已经存在的属性为:
commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)
配置Profiles,调用的是SpringApplication#configureProfiles方法.代码如下:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { environment.getActiveProfiles(); // ensure they are initialized // But these ones should go first (last wins in a property key clash) Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(profiles.toArray(new String[profiles.size()])); }
做了3件事:
调用AbstractEnvironment#getActiveProfiles获得Profile的配置.Profile配置项为spring.profiles.active
调用AbstractEnvironment#getActiveProfiles方法获取激活的Profile添加到profiles中.
设置AbstractEnvironment的activeProfiles.
AbstractEnvironment#getActiveProfiles代码如下:
public String[] getActiveProfiles() { return StringUtils.toStringArray(doGetActiveProfiles()); }
调用了doGetActiveProfiles方法.代码如下:
protected Set<String> doGetActiveProfiles() { synchronized (this.activeProfiles) { if (this.activeProfiles.isEmpty()) { String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME); if (StringUtils.hasText(profiles)) { setActiveProfiles(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(profiles))); } } return this.activeProfiles; } }
首先判断activeProfiles是否为空,如果不为空的话,就直接返回.否则调用PropertySourcesPropertyResolver#getProperty(“spring.profiles.active”) 进行查找.如果有配置的话,就加入到AbstractEnvironment的activeProfiles中.
思考 为什么要这么做?
spring boot 有一个参数优先级的概念.外置的配置优先于代码级别的.这里就是一个实现
通知所有的观察者,发送ApplicationEnvironmentPreparedEvent事件.调用的是SpringApplicationRunListeners#environmentPrepared方法.关于这里上篇文章有解释到.最终会调用EventPublishingRunListener#environmentPrepared 发送ApplicationEnvironmentPreparedEvent事件.
对ApplicationEnvironmentPreparedEvent事件感兴趣的有:
org.springframework.boot.context.config.ConfigFileApplicationListener, org.springframework.boot.context.config.AnsiOutputApplicationListener, org.springframework.boot.logging.LoggingApplicationListener, org.springframework.boot.logging.ClasspathLoggingApplicationListener, org.springframework.boot.autoconfigure.BackgroundPreinitializer, org.springframework.boot.context.config.DelegatingApplicationListener, org.springframework.boot.context.FileEncodingApplicationListener
ConfigFileApplicationListener#onApplicationEvent代码如下:
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }
调用onApplicationEnvironmentPreparedEvent方法.代码如下:
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } List<EnvironmentPostProcessor> loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); }
实现逻辑如下:
首先调用SpringFactoriesLoader加载EnvironmentPostProcessor.同时也将自己加入到postProcessors.排序后依次调用其postProcessEnvironment方法.
对于当前场景来说. EnvironmentPostProcessor如下:
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor, org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor, org.springframework.boot.context.config.ConfigFileApplicationListener
在spring-boot/META-INF/spring.factories中的配置如下:
“`
Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
“`
SpringApplicationJsonEnvironmentPostProcessor#postProcessEnvironment代码如下:
```
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
String json = environment.resolvePlaceholders(
"${spring.application.json:${SPRING_APPLICATION_JSON:}}");
if (StringUtils.hasText(json)) {
processJson(environment, json);
}
}
private void processJson(ConfigurableEnvironment environment, String json) {
try {
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(json);
if (!map.isEmpty()) {
addJsonPropertySource(environment,
new MapPropertySource("spring.application.json", flatten(map)));
}
}
catch (Exception ex) {
logger.warn("Cannot parse JSON for spring.application.json: " + json, ex);
}
}
```
实现逻辑如下:
1. 依次获取spring.application.json,SPRING_APPLICATION_JSON的值,如果没有配置的话,默认返回的空字符串.
2. 如果有配置的话,就调用processJson方法,在environment中添加MapPropertySource.name为spring.application.json.
CloudFoundryVcapEnvironmentPostProcessor#postProcessEnvironment代码如下:
```
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
Properties properties = new Properties();
addWithPrefix(properties, getPropertiesFromApplication(environment),
"vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment),
"vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources
.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
}
```
1. 首先调用CloudPlatform.CLOUD_FOUNDRY#isActive进行判断是否在CloudFoundry中.判断的逻辑为environment是否有VCAP_APPLICATION或者VCAP_SERVICES的配置 如下:
```
public boolean isActive(Environment environment) {
return environment.containsProperty("VCAP_APPLICATION")
|| environment.containsProperty("VCAP_SERVICES");
}
```
2. 如果是在CloudFoundry的话.则将vcap.application.\*, vcap.services.* 的配置加入到Properties中. 接下来判断 environment中是否有commandLineArgs的Sources.如果有的话,则添加到commandLineArgs中,否则添加名为vcap的PropertiesPropertySource.
> 一般情况下 CloudPlatform.CLOUD_FOUNDRY 返回的false.
ConfigFileApplicationListener#postProcessEnvironment代码如下:
```
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment, application);
}
```
addPropertySources方法如下:
```
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
```
做了两件事:
1. 调用了RandomValuePropertySource#addToEnvironment方法,向environment中名为systemEnvironment的添加了RandomValuePropertySource(名称为random)
代码如下:
```
/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";
public static void addToEnvironment(ConfigurableEnvironment environment) {
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
logger.trace("RandomValuePropertySource add to Environment");
}
```
正是因此我们才能在配置文件中使用${random.int} 生成随机值.
2. 初始化了Loader并调用其load方法.代码如下:
```
public void load() {
this.propertiesLoader = new PropertySourcesLoader();
this.activatedProfiles = false;
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
this.processedProfiles = new LinkedList<Profile>();
// Pre-existing active profiles set via Environment.setActiveProfiles()
// are additional profiles and config files are allowed to add more if
// they want to, so don't call addActiveProfiles() here.
Set<Profile> initialActiveProfiles = initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if (this.profiles.isEmpty()) {
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
if (!this.profiles.contains(defaultProfile)) {
this.profiles.add(defaultProfile);
}
}
}
// The default profile for these purposes is represented as null. We add it
// last so that it is first out of the queue (active profiles will then
// override any settings in the defaults when the list is reversed later).
this.profiles.add(null);
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
for (String location : getSearchLocations()) {
if (!location.endsWith("/")) {
// location is a filename already, so don't search for more
// filenames
load(location, null, profile);
}
else {
for (String name : getSearchNames()) {
load(location, name, profile);
}
}
}
this.processedProfiles.add(profile);
}
addConfigurationProperties(this.propertiesLoader.getPropertySources());
}
```
处理步骤如下:
1. 调用initializeActiveProfiles.获得ActiveProfiles.将未激活的Profiles加入到profiles中.如果profiles为空的话,就将spring.profiles.default配置的profile添加到profiles中.
2. 依次遍历profiles中的profile.依次在classpath:/,classpath:/config/,file:./,file:./config/中加载application的配置.调用ConfigFileApplicationListener$Loader#load进行加载.
3. 调用addConfigurationProperties,向environment中添加ConfigurationPropertySources.代码如下:
```
private void addConfigurationProperties(MutablePropertySources sources) {
List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();
for (PropertySource<?> item : sources) {
reorderedSources.add(item);
}
addConfigurationProperties(
new ConfigurationPropertySources(reorderedSources));
}
private void addConfigurationProperties(
ConfigurationPropertySources configurationSources) {
MutablePropertySources existingSources = this.environment
.getPropertySources();
if (existingSources.contains(DEFAULT_PROPERTIES)) {
existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);
}
else {
existingSources.addLast(configurationSources);
}
}
```
initializeActiveProfiles代码如下:
```
private Set<Profile> initializeActiveProfiles() {
// 1. 如果environment不含有spring.profiles.active和spring.profiles.include的配置话,返回空集合
if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
return Collections.emptySet();
}
// Any pre-existing active profiles set via property sources (e.g. System
// properties) take precedence over those added in config files.
// 2. 调用bindSpringProfiles,生成SpringProfiles
SpringProfiles springProfiles = bindSpringProfiles(
this.environment.getPropertySources());
Set<Profile> activeProfiles = new LinkedHashSet<Profile>(
springProfiles.getActiveProfiles());
activeProfiles.addAll(springProfiles.getIncludeProfiles());
// 3. 调用maybeActivateProfiles.将activatedProfiles设为true
maybeActivateProfiles(activeProfiles);
return activeProfiles;
}
```
代码的逻辑如下:
1. 如果environment不含有spring.profiles.active和spring.profiles.include的配置话,返回空集合
> 注意: 当前的environment拥有的source有
commandLineArgs、servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment, RandomValuePropertySource 如果想
不返回空的话,就需要在以上的source中有配置.最简单的方式是通过命令行的方式传入 --spring.profiles.active=you profiles 即可
2. 调用bindSpringProfiles,生成SpringProfiles
3. 调用maybeActivateProfiles.将activatedProfiles设为true
bindSpringProfiles代码如下:
```
private SpringProfiles bindSpringProfiles(PropertySources propertySources) {
SpringProfiles springProfiles = new SpringProfiles();
RelaxedDataBinder dataBinder = new RelaxedDataBinder(springProfiles,
"spring.profiles");
dataBinder.bind(new PropertySourcesPropertyValues(propertySources, false));
springProfiles.setActive(resolvePlaceholders(springProfiles.getActive()));
springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));
return springProfiles;
}
```
逻辑如下:
1. 初始化SpringProfiles和RelaxedDataBinder.RelaxedDataBinder读取的配置是前缀为 spring.profiles的配置.
2. 实例化PropertySourcesPropertyValues,调用DataBinder#bind进行数据的绑定.
3. 设置SpringProfiles的Active和Include属性.
其中第2步中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);
}
}
```
其中最主要的代码是 初始化了 PropertySourcesPropertyResolver.并依次遍历environment中的PropertySources.调用processPropertySource方法进行处理.
> 注意
> 这里的source为 [SimpleCommandLinePropertySource {name='commandLineArgs'},
StubPropertySource {name=’servletConfigInitParams’},
StubPropertySource {name=’servletContextInitParams’},
MapPropertySource {name=’systemProperties’},
SystemEnvironmentPropertySource {name=’systemEnvironment’},
RandomValuePropertySource {name=’random’}]
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);
}
}
这里的逻辑很简单:
如果 source 为 CompositePropertySource的话,调用processCompositePropertySource
- 如果 source 为 EnumerablePropertySource 的话,调用processEnumerablePropertySource
否则 调用 processNonEnumerablePropertySource
这里我们需要看下各个source的类图.分别如下:
SimpleCommandLinePropertySource 类图为:
StubPropertySource 类图为:
MapPropertySource 类图为:
SystemEnvironmentPropertySource 类图为:
RandomValuePropertySource 类图为:
因此我们就明白了:对于 SimpleCommandLinePropertySource, MapPropertySource, SystemEnvironmentPropertySource 会调用processEnumerablePropertySource方法.
对于 StubPropertySource, RandomValuePropertySource 会调用 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)) { Object value = getEnumerableProperty(source, resolver, propertyName); putIfAbsent(propertyName, value, source); } } } }
逻辑很简单,首先判断source是否有值. 如果有的话,依次遍历之.然后调用PropertyNamePatternsMatcher的matches方法判断是否匹配.如果匹配的话,就进行加入.这里的
PropertyNamePatternsMatcher为PropertyNamePatternsMatcher.ALL 其matches方法是直接返回true的.代码如下:@Override public boolean matches(String propertyName) { return true; }
而 processNonEnumerablePropertySource 代码如下:
private void processNonEnumerablePropertySource(PropertySource<?> source, PropertySourcesPropertyResolver resolver) { // We can only do exact matches for non-enumerable property names, but // that's better than nothing... if (this.nonEnumerableFallbackNames == null) { return; } for (String propertyName : this.nonEnumerableFallbackNames) { if (!source.containsProperty(propertyName)) { continue; } Object value = null; try { value = resolver.getProperty(propertyName, Object.class); } catch (RuntimeException ex) { // Probably could not convert to Object, weird, but ignorable } if (value == null) { value = source.getProperty(propertyName.toUpperCase()); } putIfAbsent(propertyName, value, source); } }
这里首先判断nonEnumerableFallbackNames是否为null,如果为null的话,直接return.悲催的是,这里的nonEnumerableFallbackNames 为null.因此不会执行后续的代码.
接下来调用DataBinder#bind方法.代码如下:
public void bind(PropertyValues pvs) { MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs); doBind(mpvs); }
这里做了2件事:
判断PropertyValues是否为MutablePropertyValues.如果不是,实例化MutablePropertyValues. 不幸的是这里传入的PropertySourcesPropertyValues 不是MutablePropertyValues的实例,因此会实例化MutablePropertyValues. PropertySourcesPropertyValues类图如下:
MutablePropertyValues 构造器如下:
public MutablePropertyValues(PropertyValues original) { // We can optimize this because it's all new: // There is no replacement of existing property values. if (original != null) { PropertyValue[] pvs = original.getPropertyValues(); this.propertyValueList = new ArrayList<PropertyValue>(pvs.length); for (PropertyValue pv : pvs) { this.propertyValueList.add(new PropertyValue(pv)); } } else { this.propertyValueList = new ArrayList<PropertyValue>(0); } }
逻辑很简单,首先判断PropertyValues是否为null.如果不为null的话,依次遍历PropertyValues的PropertyValues.加入到MutablePropertyValues中的propertyValueList.这样MutablePropertyValues就变相的拥有了environment中source的配置。
调用 doBind 进行绑定.代码如下:
protected void doBind(MutablePropertyValues mpvs) { checkAllowedFields(mpvs); checkRequiredFields(mpvs); applyPropertyValues(mpvs); }
作了3件事
- 检查是否包含不允许的字段
- 检查RequiredFields 是否不存在,如果不存在的话,就向error processor 添加FieldError.
调用applyPropertyValues方法进行处理.代码如下:
protected void applyPropertyValues(MutablePropertyValues mpvs) { try { // Bind request parameters onto target object. getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields()); } catch (PropertyBatchUpdateException ex) { // Use bind error processor to create FieldErrors. for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) { getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult()); } } }
调用
protected ConfigurablePropertyAccessor getPropertyAccessor() { return getInternalBindingResult().getPropertyAccessor(); }
调用
protected AbstractPropertyBindingResult getInternalBindingResult() { if (this.bindingResult == null) { initBeanPropertyAccess(); } return this.bindingResult; }
由于此时bindingResult为null,因此调用initBeanPropertyAccess方法进行初始化.代码如下:
public void initBeanPropertyAccess() { Assert.state(this.bindingResult == null, "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods"); this.bindingResult = createBeanPropertyBindingResult(); }
调用createBeanPropertyBindingResult方法.代码如下:
protected AbstractPropertyBindingResult createBeanPropertyBindingResult() { BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit()); if (this.conversionService != null) { result.initConversion(this.conversionService); } if (this.messageCodesResolver != null) { result.setMessageCodesResolver(this.messageCodesResolver); } return result; }
因此这里创建了 BeanPropertyBindingResult.
然后调用BeanPropertyBindingResult#getPropertyAccessor方法.代码如下:
public final ConfigurablePropertyAccessor getPropertyAccessor() { if (this.beanWrapper == null) { this.beanWrapper = createBeanWrapper(); this.beanWrapper.setExtractOldValueForEditor(true); this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths); this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit); } return this.beanWrapper; }
由于这里的beanWrapper为null,因此调用createBeanWrapper方法进行初始化.代码如下:
protected BeanWrapper createBeanWrapper() { Assert.state(this.target != null, "Cannot access properties on null bean instance '" + getObjectName() + "'!"); return PropertyAccessorFactory.forBeanPropertyAccess(this.target); }
调用
public static BeanWrapper forBeanPropertyAccess(Object target) { return new BeanWrapperImpl(target); }
在获得PropertyAccessor后,调用其setPropertyValues方法.代码如下:
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { List<PropertyAccessException> propertyAccessExceptions = null; List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); for (PropertyValue pv : propertyValues) { try { // This method may throw any BeansException, which won't be caught // here, if there is a critical failure such as no matching field. // We can attempt to deal only with less serious exceptions. setPropertyValue(pv); } catch (NotWritablePropertyException ex) { if (!ignoreUnknown) { throw ex; } // Otherwise, just ignore it and continue... } catch (NullValueInNestedPathException ex) { if (!ignoreInvalid) { throw ex; } // Otherwise, just ignore it and continue... } catch (PropertyAccessException ex) { if (propertyAccessExceptions == null) { propertyAccessExceptions = new LinkedList<PropertyAccessException>(); } propertyAccessExceptions.add(ex); } } if (propertyAccessExceptions != null) { PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[propertyAccessExceptions.size()]); throw new PropertyBatchUpdateException(paeArray); } }
逻辑很简单,遍历propertyValues.调用setPropertyValue方法进行处理.如果在处理过程中,出现了PropertyAccessException异常,就加入到propertyAccessExceptions中.最后判断是否propertyAccessExceptions不为空.如果是的话,抛出PropertyBatchUpdateException异常.
调用RelaxedBeanWrapper#setPropertyValue方法.代码如下:
public void setPropertyValue(PropertyValue pv) throws BeansException { try { super.setPropertyValue(pv); } catch (NotWritablePropertyException ex) { PropertyOrigin origin = OriginCapablePropertyValue.getOrigin(pv); if (isBenign(origin)) { logger.debug("Ignoring benign property binding failure", ex); return; } if (origin == null) { throw ex; } throw new RelaxedBindingNotWritablePropertyException(ex, origin); } }
调用 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); } }
调用
protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { if (tokens.keys != null) { processKeyedProperty(tokens, pv); } else { processLocalProperty(tokens, pv); } }
这里只拿processLocalProperty举例. 如果PropertyValue 需要进行转换的话,就会调用propertyEditorRegistry中的ConversionService进行转换.这里是spring的知识了.就不再深入了.
我们把视线回到ConfigFileApplicationListener$Loader#bindSpringProfiles方法.现在我们该执行如下代码了:
resolvePlaceholders代码如下:
private List<String> resolvePlaceholders(List<String> values) { List<String> resolved = new ArrayList<String>(); for (String value : values) { resolved.add(this.environment.resolvePlaceholders(value)); } return resolved; }
逻辑很简单,遍历Active Profiles .调用 AbstractEnvironment#resolvePlaceholders 将形如 ${Placeholder} 进行解析.将解析后的值加入到resolved中进行返回.
AbstractEnvironment#resolvePlaceholders 代码如下:
public String resolvePlaceholders(String text) { if (this.nonStrictHelper == null) { this.nonStrictHelper = createPlaceholderHelper(true); } return doResolvePlaceholders(text, this.nonStrictHelper); }
调用
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { @Override public String resolvePlaceholder(String placeholderName) { return getPropertyAsRawString(placeholderName); } }); }
调用
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); int startIndex = value.indexOf(this.placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the placeholder key. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); }
这里的逻辑很简单.首先判断是否有${ ,如果有的话,就进行替换.否则直接返回.这里涉及Placeholder的解析.我们后续会有文章进行分析的.
接下来我们为SpringProfiles设置Include,不过这里springProfiles#getInclude返回的是空集合,因此这里就不在分析了.代码如下:
springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));
经过漫长的道路,我们来到了maybeActivateProfiles方法.代码如下:
private void maybeActivateProfiles(Set<Profile> profiles) { if (this.activatedProfiles) { if (!profiles.isEmpty()) { this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied"); } return; } if (!profiles.isEmpty()) { addProfiles(profiles); this.logger.debug("Activated profiles " + StringUtils.collectionToCommaDelimitedString(profiles)); this.activatedProfiles = true; // 删除默认的Profiles removeUnprocessedDefaultProfiles(); } }
逻辑很简单:
- 如果activatedProfiles为true的话,代表已经激活过了,就不再继续后续处理.直接return.
否则调用addProfiles方法.将activate Profiles 加入到Loader中的profiles中.代码如下:
private void addProfiles(Set<Profile> profiles) { for (Profile profile : profiles) { this.profiles.add(profile); // 如果environment中的ActiveProfile和profile不等的话调用prependProfile. if (!environmentHasActiveProfile(profile.getName())) { // If it's already accepted we assume the order was set // intentionally prependProfile(this.environment, profile); } } }
- 将activatedProfiles设为true.
删除默认的Profiles.代码如下:
private void removeUnprocessedDefaultProfiles() { for (Iterator<Profile> iterator = this.profiles.iterator(); iterator .hasNext();) { if (iterator.next().isDefaultProfile()) { iterator.remove(); } } }
如何判断Profile为默认的呢? 调用Profile#isDefaultProfile方法.代码如下:
public boolean isDefaultProfile() { return this.defaultProfile; }
而defaultProfile默认为false.
我们继续分析ConfigFileApplicationListener$Loader#load方法中的第2步–>加载配置.代码如下:
while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); for (String location : getSearchLocations()) { if (!location.endsWith("/")) { // location is a filename already, so don't search for more // filenames load(location, null, profile); } else { for (String name : getSearchNames()) { load(location, name, profile); } } } this.processedProfiles.add(profile); }
逻辑如下:
- 遍历profiles
在SearchLocations中依次进行加载profile的配置.这里有判断location是否是已/结尾的.如果不是以/结尾的,就意味着明确了文件的路径,直接加载即可.否则遍历location下的SearchName进行加载.
getSearchLocations代码如下:
private Set<String> getSearchLocations() { Set<String> locations = new LinkedHashSet<String>(); // User-configured settings take precedence, so we do them first if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { for (String path : asResolvedSet( this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); if (!ResourceUtils.isUrl(path)) { path = ResourceUtils.FILE_URL_PREFIX + path; } } locations.add(path); } } locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; }
首先判断environment是否有spring.config.location的配置,如果有的话就加入的搜索路径中.然后加入默认的路径–>classpath:/,classpath:/config/,file:./,file:./config/
getSearchNames 代码如下:
private Set<String> getSearchNames() { if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null); } return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); }
还是同样的套路,如果environment有配置spring.config.name的话,就用配置的.否则就返回默认的application的.
到这里我们就明白了. 在classpath:/,classpath:/config/,file:./,file:./config/ 路径下,加载 application-profile 的配置文件.
加载代码如下:
private void load(String location, String name, Profile profile) { String group = "profile=" + (profile == null ? "" : profile); if (!StringUtils.hasText(name)) { // Try to load directly from the location loadIntoGroup(group, location, profile); } else { // Search for a file with the given name for (String ext : this.propertiesLoader.getAllFileExtensions()) { if (profile != null) { // Try the profile-specific file loadIntoGroup(group, location + name + "-" + profile + "." + ext, null); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) { loadIntoGroup(group, location + name + "-" + processedProfile + "." + ext, profile); } } // Sometimes people put "spring.profiles: dev" in // application-dev.yml (gh-340). Arguably we should try and error // out on that, but we can be kind and load it anyway. loadIntoGroup(group, location + name + "-" + profile + "." + ext, profile); } // Also try the profile-specific section (if any) of the normal file loadIntoGroup(group, location + name + "." + ext, profile); } } }
逻辑如下:
- 如果name等于null的话,直接调用loadIntoGroup
循环遍历可加载文件的扩展名,对于当前场景来说,可加载的扩展名为–> properties, xml, yml, yaml.
如果profile不等于null的话, 调用loadIntoGroup,加载的location为 location/application-profile.ext
循环遍历processedProfiles, 调用loadIntoGroup,加载的location为 location/application-processedProfile.ext
调用loadIntoGroup,加载的location为 location/application-pprofile.ext,这是因为人们有时会在application-dev.yml 中配置 spring.profiles: dev 这种情况下,也应该进行加载.
最后,调用loadIntoGroup,加载的location为location/application.txt.
loadIntoGroup方法没有做什么,直接调用的doLoadIntoGroup方法.代码如下:
private PropertySource<?> doLoadIntoGroup(String identifier, String location, Profile profile) throws IOException { Resource resource = this.resourceLoader.getResource(location); PropertySource<?> propertySource = null; StringBuilder msg = new StringBuilder(); if (resource != null && resource.exists()) { String name = "applicationConfig: [" + location + "]"; String group = "applicationConfig: [" + identifier + "]"; propertySource = this.propertiesLoader.load(resource, group, name, (profile == null ? null : profile.getName())); if (propertySource != null) { msg.append("Loaded "); handleProfileProperties(propertySource); } else { msg.append("Skipped (empty) "); } } else { msg.append("Skipped "); } msg.append("config file "); msg.append(getResourceDescription(location, resource)); if (profile != null) { msg.append(" for profile ").append(profile); } if (resource == null || !resource.exists()) { msg.append(" resource not found"); this.logger.trace(msg); } else { this.logger.debug(msg); } return propertySource; }
代码比较长,但是有用的是:
- 调用resourceLoader进行加载.
- 如果加载成功的话,调用propertiesLoader进行加载.如果加载成功的话,调用handleProfileProperties方法,
PropertySourcesLoader#load方法如下:
public PropertySource<?> load(Resource resource, String group, String name, String profile) throws IOException { if (isFile(resource)) { String sourceName = generatePropertySourceName(name, profile); for (PropertySourceLoader loader : this.loaders) { if (canLoadFileExtension(loader, resource)) { PropertySource<?> specific = loader.load(sourceName, resource, profile); addPropertySource(group, specific, profile); return specific; } } } return null; }
逻辑如下:
- 如果是文件的话,就调用PropertySourceLoader尝试加载.否则直接返回null.
生成sourceName.生成规则为
(profile == null ? name : name + “#” + profile)
遍历PropertySourceLoader,如果能进行加载的话,调用其load方法进行加载.然后将其返回值PropertySource加入到PropertySourcesLoader#propertySources中.
这里有必要说明一下PropertySourcesLoader中的loaders. 该类的初始化调用链如下:
其构造器如下:
public PropertySourcesLoader(MutablePropertySources propertySources) { Assert.notNull(propertySources, "PropertySources must not be null"); this.propertySources = propertySources; this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); }
可以发现loaders 是通过SpringFactoriesLoader加载META-INF/spring.factories中org.springframework.boot.env.PropertySourceLoader的配置.其中,配置如下:
# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader
至此,我们就知道了loaders 为 PropertiesPropertySourceLoader,YamlPropertySourceLoader
canLoadFileExtension 代码如下:
private boolean canLoadFileExtension(PropertySourceLoader loader, Resource resource) { String filename = resource.getFilename().toLowerCase(); for (String extension : loader.getFileExtensions()) { if (filename.endsWith("." + extension.toLowerCase())) { return true; } } return false; }
可以发现是通过比较PropertySourceLoader支持的文件扩展名是否和当前的文件的后缀相同进行判断.
通过源码可知.PropertiesPropertySourceLoader支持的扩展名为–> properties, xml.
YamlPropertySourceLoader支持的扩展名为–> yml, yaml.接下来我们分别看下PropertySourceLoader中load方法的实现.
PropertiesPropertySourceLoader#load方法如下:
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException { if (profile == null) { Properties properties = PropertiesLoaderUtils.loadProperties(resource); if (!properties.isEmpty()) { return new PropertiesPropertySource(name, properties); } } return null; }
直接通过PropertiesLoaderUtils直接进行加载,最后返回PropertiesPropertySource.
YamlPropertySourceLoader#load 代码如下:
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException { if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { Processor processor = new Processor(resource, profile); Map<String, Object> source = processor.process(); if (!source.isEmpty()) { return new MapPropertySource(name, source); } } return null; }
首先判断当前类路径是否存在org.yaml.snakeyaml.Yaml.如果存在的话,再进行加载,成功后返回MapPropertySource.
加载完毕后,我们来到了 PropertySourcesLoader#addPropertySource.代码如下:
private void addPropertySource(String basename, PropertySource<?> source, String profile) { if (source == null) { return; } if (basename == null) { this.propertySources.addLast(source); return; } EnumerableCompositePropertySource group = getGeneric(basename); group.add(source); logger.trace("Adding PropertySource: " + source + " in group: " + basename); if (this.propertySources.contains(group.getName())) { this.propertySources.replace(group.getName(), group); } else { this.propertySources.addFirst(group); } }
做了4件事:
- 如果source加载失败 直接返回
- 如果basename没有指定,直接加入propertySources后返回.一般basename都是有值的
- 从propertySources中获得EnumerableCompositePropertySource后加入source
- 加入propertySources,如果之前存在的话,就进行替换
加载完毕后,加入到processedProfiles中.
我们继续分析ConfigFileApplicationListener$Loader#load方法中的第3步–>调用addConfigurationProperties.代码如下:
private void addConfigurationProperties(MutablePropertySources sources) { List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>(); for (PropertySource<?> item : sources) { reorderedSources.add(item); } addConfigurationProperties( new ConfigurationPropertySources(reorderedSources)); } private void addConfigurationProperties( ConfigurationPropertySources configurationSources) { MutablePropertySources existingSources = this.environment .getPropertySources(); if (existingSources.contains(DEFAULT_PROPERTIES)) { existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources); } else { existingSources.addLast(configurationSources); } }
逻辑很简单,如果environment中有defaultProperties的话,就添加ConfigurationPropertySources到defaultProperties.否则直接进行添加.
在笔者的测试环境中,是直接加入到environment中的.因此现在environment中有如下source:
[SimpleCommandLinePropertySource {name=’commandLineArgs’}, StubPropertySource {name=’servletConfigInitParams’}, StubPropertySource {name=’servletContextInitParams’}, MapPropertySource {name=’systemProperties’}, SystemEnvironmentPropertySource {name=’systemEnvironment’}, RandomValuePropertySource {name=’random’}, ConfigurationPropertySources {name=’applicationConfigurationProperties’}]终于我们分析完了 ConfigFileApplicationListener中的postProcessEnvironment的第一步.不容易啊. 接下来我们分析第2步–> 调用ConfigFileApplicationListener#configureIgnoreBeanInfo,完成spring.beaninfo.ignore的配置,一般默认为true.代码如下:
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore"; private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { if (System.getProperty( CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, "spring.beaninfo."); Boolean ignore = resolver.getProperty("ignore", Boolean.class, Boolean.TRUE); System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } }
ConfigFileApplicationListener对于 postProcessEnvironment处理的第三步是调用 bindToSpringApplication方法.代码如下:
protected void bindToSpringApplication(ConfigurableEnvironment environment, SpringApplication application) { PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>( application); binder.setTargetName("spring.main"); binder.setConversionService(this.conversionService); binder.setPropertySources(environment.getPropertySources()); try { binder.bindPropertiesToTarget(); } catch (BindException ex) { throw new IllegalStateException("Cannot bind to SpringApplication", ex); } }
3件事:
初始化PropertiesConfigurationFactory, PropertiesConfigurationFactory实现了FactoryBean,MessageSourceAware,InitializingBean 接口. 其构造器将SpringApplication传入了进去,这样PropertiesConfigurationFactory就持有了SpringApplication的实例.代码如下:
public PropertiesConfigurationFactory(T target) { Assert.notNull(target, "target must not be null"); this.target = target; }
设置PropertiesConfigurationFactory的TargetName,ConversionService,PropertySources.代码如下:
binder.setTargetName("spring.main"); binder.setConversionService(this.conversionService); binder.setPropertySources(environment.getPropertySources());
这里很明显 将PropertiesConfigurationFactory的TargetName 设为 spring.main,
将environment中的PropertySources赋值给PropertiesConfigurationFactory.
最后将ConfigFileApplicationListener中的conversionService进行赋值.这里有必要说一下 ConfigFileApplicationListener 中的conversionService.
该属性是在ConfigFileApplicationListener实例化时进行赋值的.代码如下:private final ConversionService conversionService = new DefaultConversionService();
DefaultConversionService 的构造器如下:
public DefaultConversionService() { addDefaultConverters(this); }
调用
public static void addDefaultConverters(ConverterRegistry converterRegistry) { addScalarConverters(converterRegistry); addCollectionConverters(converterRegistry); converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry)); if (jsr310Available) { Jsr310ConverterRegistrar.registerJsr310Converters(converterRegistry); } converterRegistry.addConverter(new ObjectToObjectConverter()); converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new FallbackObjectToStringConverter()); if (javaUtilOptionalClassAvailable) { converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry)); } }
做了6件事
- 加入标准的转换器,如NumberToNumberConverterFactory.
- 加入集合的转换器,如ArrayToCollectionConverter
- 加入ByteBufferConverter
如果当前类路径存在java.time.ZoneId的话,加入如下转换器:
public static void registerJsr310Converters(ConverterRegistry converterRegistry) { converterRegistry.addConverter(new StringToTimeZoneConverter()); converterRegistry.addConverter(new ZoneIdToTimeZoneConverter()); converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter()); }
- 加入ObjectToObjectConverter,IdToEntityConverter,FallbackObjectToStringConverter
- 如果当前类路径存在java.util.Optional的话,加入ObjectToOptionalConverter
调用PropertiesConfigurationFactory#bindPropertiesToTarget方法.代码如下:
public void bindPropertiesToTarget() throws BindException { Assert.state(this.propertySources != null, "PropertySources should not be null"); try { if (logger.isTraceEnabled()) { logger.trace("Property Sources: " + this.propertySources); } this.hasBeenBound = true; 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 { RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(this.target, this.targetName) : new RelaxedDataBinder(this.target)); 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); customizeBinder(dataBinder); Iterable<String> relaxedTargetNames = getRelaxedTargetNames(); Set<String> names = getNames(relaxedTargetNames); PropertyValues propertyValues = getPropertySourcesPropertyValues(names, relaxedTargetNames); dataBinder.bind(propertyValues); if (this.validator != null) { dataBinder.validate(); } checkForBindingErrors(dataBinder); }
做了7件事:
初始化RelaxedDataBinder 并进行设置一下属性.对于当前场景来说targetName = spring.main,target = SpringApplication.这样RelaxedDataBinder也就持有了SpringApplication.
其次 由于当前的validator还是为null.因此RelaxedDataBinder的Validator不会进行设置.
最后将PropertiesConfigurationFactory持有的转换器赋值给RelaxedDataBinder.
执行customizeBinder.这是一个扩展点,空实现
执行getRelaxedTargetNames方法,获得relaxedTargetNames,对于当前来说,其值为–>spring.main.代码如下:
private Iterable<String> getRelaxedTargetNames() { return (this.target != null && StringUtils.hasLength(this.targetName) ? new RelaxedNames(this.targetName) : null); }
调用getNames.代码如下:
private Set<String> getNames(Iterable<String> prefixes) { Set<String> names = new LinkedHashSet<String>(); if (this.target != null) { PropertyDescriptor[] descriptors = BeanUtils .getPropertyDescriptors(this.target.getClass()); for (PropertyDescriptor descriptor : descriptors) { String name = descriptor.getName(); if (!name.equals("class")) { RelaxedNames relaxedNames = RelaxedNames.forCamelCase(name); if (prefixes == null) { for (String relaxedName : relaxedNames) { names.add(relaxedName); } } else { for (String prefix : prefixes) { for (String relaxedName : relaxedNames) { names.add(prefix + "." + relaxedName); names.add(prefix + "_" + relaxedName); } } } } } } return names; }
通过遍历target的属性,这里的target为SpringApplication.然后将SpringApplication的属性按照单词划分的规则,与relaxedTargetNames进行拼接
举例说明:SpringApplication中有一个logStartupInfo属性,则拆分为log-startup-info,然后与spring.main拼接为spring.main.log-startup-info 和 spring.main_log-startup-info
SpringApplication中持有的属性有19个,如下:
org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=addCommandLineProperties], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=additionalProfiles], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=applicationContextClass], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=banner], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=bannerMode], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=beanNameGenerator], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=class], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=classLoader], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=defaultProperties], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=environment], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=headless], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=initializers], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=listeners], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=logStartupInfo], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=mainApplicationClass], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=registerShutdownHook], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=resourceLoader], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=sources], org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=webEnvironment]
调用getPropertySourcesPropertyValues方法,生成PropertyValues.代码如下:
private PropertyValues getPropertySourcesPropertyValues(Set<String> names, Iterable<String> relaxedTargetNames) { PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names, relaxedTargetNames); return new PropertySourcesPropertyValues(this.propertySources, names, includes, this.resolvePlaceholders); }
调用getPropertyNamePatternsMatcher方法生成PropertyNamePatternsMatcher.由于ignoreUnknownFields等于true并且target为SpringApplication不是map的子类.因此返回的是DefaultPropertyNamePatternsMatcher.
代码如下:
private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names, Iterable<String> relaxedTargetNames) { 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); } 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 return PropertyNamePatternsMatcher.ALL; }
最后直接生成PropertySourcesPropertyValues. 这个其初始化过程我们已经分析过了.不同的是.现在PropertySourcesPropertyValues 持有的PropertySources为
[SimpleCommandLinePropertySource {name=’commandLineArgs’}, StubPropertySource {name=’servletConfigInitParams’}, StubPropertySource {name=’servletContextInitParams’}, MapPropertySource {name=’systemProperties’}, SystemEnvironmentPropertySource {name=’systemEnvironment’}, RandomValuePropertySource {name=’random’}, ConfigurationPropertySources {name=’applicationConfigurationProperties’}]
这里比之前的新增了一个 ConfigurationPropertySources,其类图如下:
由之前可知,会调用processEnumerablePropertySource方法进行处理.- 调用bind,进行绑定.这里的代码我们之前已经分析过了.这里就不在赘述了.
- 检查在绑定过程中是否出现异常,如果有的话,抛出BindException
经过长长的分析,ConfigFileApplicationListener的postProcessEnvironment就分析完了.不过这里有个思考.为何费这么大的劲生成一个PropertiesConfigurationFactory.这个我的想法是 由于PropertiesConfigurationFactory 实现了 FactoryBean,InitializingBean 接口.而其getObject实现如下:
public T getObject() throws Exception { if (!this.hasBeenBound) { bindPropertiesToTarget(); } return this.target;}
因此当获取的时候,就会获得处理过后的SpringApplication
AnsiOutputApplicationListener#onApplicationEvent,代码如下:
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( event.getEnvironment(), "spring.output.ansi."); if (resolver.containsProperty("enabled")) { String enabled = resolver.getProperty("enabled"); AnsiOutput.setEnabled(Enum.valueOf(Enabled.class, enabled.toUpperCase())); } if (resolver.containsProperty("console-available")) { AnsiOutput.setConsoleAvailable( resolver.getProperty("console-available", Boolean.class)); } }
- 如果配置了spring.output.ansi.enabled,则调用AnsiOutput#setEnabled
- 如果设置了spring.output.ansi.console-available,则调用AnsiOutput#setConsoleAvailable
由于默认情况下,我们没有配置spring.output.ansi.enabled,spring.output.ansi.console-available,因此,AnsiOutputApplicationListener相当于空操作
LoggingApplicationListener#onApplicationEvent 这部分的内容请看我们的后续文章:
ClasspathLoggingApplicationListener#onApplicationEvent–> 如果loger的debug级别可用的话,则打印日志:
public void onApplicationEvent(ApplicationEvent event) { if (logger.isDebugEnabled()) { if (event instanceof ApplicationEnvironmentPreparedEvent) { logger.debug("Application started with classpath: " + getClasspath()); } else if (event instanceof ApplicationFailedEvent) { logger.debug( "Application failed to start with classpath: " + getClasspath()); } } }
默认情况下,由于logger的级别是Info,不支持debug,因此,ClasspathLoggingApplicationListener相当于空操作
BackgroundPreinitializer#onApplicationEvent,最终调用了performPreinitialization方法,代码如下:
private void performPreinitialization() { try { Thread thread = new Thread(new Runnable() { @Override public void run() { runSafely(new MessageConverterInitializer()); runSafely(new MBeanFactoryInitializer()); runSafely(new ValidationInitializer()); runSafely(new JacksonInitializer()); runSafely(new ConversionServiceInitializer()); preinitializationComplete.countDown(); } public void runSafely(Runnable runnable) { try { runnable.run(); } catch (Throwable ex) { // Ignore } } }, "background-preinit"); thread.start(); } catch (Exception ex) { // This will fail on GAE where creating threads is prohibited. We can safely // continue but startup will be slightly slower as the initialization will now // happen on the main thread. preinitializationComplete.countDown(); } }
调用了MessageConverterInitializer#run,代码如下:
private static class MessageConverterInitializer implements Runnable { @Override public void run() { new AllEncompassingFormHttpMessageConverter(); } }
AllEncompassingFormHttpMessageConverter构造器如下:
public AllEncompassingFormHttpMessageConverter() { addPartConverter(new SourceHttpMessageConverter<Source>()); if (jaxb2Present && !jackson2XmlPresent) { addPartConverter(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2Present) { addPartConverter(new MappingJackson2HttpMessageConverter()); } else if (gsonPresent) { addPartConverter(new GsonHttpMessageConverter()); } if (jackson2XmlPresent) { addPartConverter(new MappingJackson2XmlHttpMessageConverter()); } }
添加了一系列的Converter
MBeanFactoryInitializer#run,代码如下:
private static class MBeanFactoryInitializer implements Runnable { @Override public void run() { new MBeanFactory(); } }
实例化了MBeanFactory
ValidationInitializer,代码如下:
private static class ValidationInitializer implements Runnable { @Override public void run() { Validation.byDefaultProvider().configure(); } }
JacksonInitializer,代码如下:
private static class JacksonInitializer implements Runnable { @Override public void run() { Jackson2ObjectMapperBuilder.json().build(); } }
ConversionServiceInitializer,代码如下:
private static class ConversionServiceInitializer implements Runnable { @Override public void run() { new DefaultFormattingConversionService(); } }
关于这部分的内容,都是属于spring的范畴,读者可以查阅相关资料了解,此处就不展开了
DelegatingApplicationListener#onApplicationEvent,代码如下:
if (event instanceof ApplicationEnvironmentPreparedEvent) { List<ApplicationListener<ApplicationEvent>> delegates = getListeners( ((ApplicationEnvironmentPreparedEvent) event).getEnvironment()); if (delegates.isEmpty()) { return; } this.multicaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<ApplicationEvent> listener : delegates) { this.multicaster.addApplicationListener(listener); } }
由于默认情况下, delegates为空,因此,不会执行后续操作
FileEncodingApplicationListener#onApplicationEvent,代码如下:
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( event.getEnvironment(), "spring."); if (resolver.containsProperty("mandatoryFileEncoding")) { String encoding = System.getProperty("file.encoding"); String desired = resolver.getProperty("mandatoryFileEncoding"); if (encoding != null && !desired.equalsIgnoreCase(encoding)) { logger.error("System property 'file.encoding' is currently '" + encoding + "'. It should be '" + desired + "' (as defined in 'spring.mandatoryFileEncoding')."); logger.error("Environment variable LANG is '" + System.getenv("LANG") + "'. You could use a locale setting that matches encoding='" + desired + "'."); logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL") + "'. You could use a locale setting that matches encoding='" + desired + "'."); throw new IllegalStateException( "The Java Virtual Machine has not been configured to use the " + "desired default character encoding (" + desired + ")."); } } }
如果配置了spring.mandatoryFileEncoding
- 如果配置了系统属性–>file.encoding 判断spring.mandatoryFileEncoding 是否和属性配置的不一样,如果不一样,则抛出异常
默认情况下,spring.mandatoryFileEncoding 没有配置,因此,相当于是空操作.
至此. SpringApplication run 方法中的第4步.后续会继续分析接下来的步骤.