1.接着來 ConfigFileApplicationListener中的postProcessEnvironment,等等,我差點要跳過了,原來這個EnvironmentPostProcessor纔是加載配置文件的關鍵1
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment, application);
}
2.進入 addPropertySources方法,入參environment 爲StandardServletEnvironment
resourceLoader 爲 null,
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
2.1且看RandomValuePropertySource.addToEnvironment(environment);方法
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");
}
這個方法就是將RandomValuePropertySource加在SystemEnvironmentPropertySource {name='systemEnvironment'}之後
的第一個
2.2 再看 new Loader(environment, resourceLoader)方法
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader()
: resourceLoader;
}
注意這個Loader是ConfigFileApplicationListener中的內部類
該類的註釋爲Loads candidate property sources and configures the active profiles.
中文的意思是加載候選屬性源並配置活動配置文件
這裏的resourceLoader 附上了 new DefaultResourceLoader(),已不再爲null
2.3 重點就是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());
}
2.3.1 來看 Set<Profile> initialActiveProfiles = initializeActiveProfiles();
private Set<Profile> initializeActiveProfiles() {
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.
SpringProfiles springProfiles = bindSpringProfiles(
this.environment.getPropertySources());
Set<Profile> activeProfiles = new LinkedHashSet<Profile>(
springProfiles.getActiveProfiles());
activeProfiles.addAll(springProfiles.getIncludeProfiles());
maybeActivateProfiles(activeProfiles);
return activeProfiles;
}
這個方法就是獲取environment中 spring.profiles.active和spring.profiles.include的值並封裝成initialActiveProfiles
2.3.2 this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
getUnprocessedActiveProfiles(initialActiveProfiles)顧名思義,獲取不能處理的激活文件,比如在key不是
spring.profiles.active和spring.profiles.include的環境
如果 this.profiles這都沒有值 ,spring 會添加一個default 的profile,然後在指定的location循環遍歷找配置文件
這個location的生存如下,在沒有配置 spring.config.location的情況下,生成的location如下
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;
}
那它要讀哪些文件呢getSearchNames方法給出了答案,在沒有指定spring.config.name的值得時候
答案是[application] 也就是平時所說的application文件
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);
}
文件名有了,那文件的後綴名,默認爲[properties, xml, yml, yaml]
public Set<String> getAllFileExtensions() {
Set<String> fileExtensions = new LinkedHashSet<String>();
for (PropertySourceLoader loader : this.loaders) {
fileExtensions.addAll(Arrays.asList(loader.getFileExtensions()));
}
return fileExtensions;
}
具體的加載方法爲private void load(String location, String name, Profile 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);
}
}
}
2.3.3 在看看關鍵的loadIntoGroup(group, location, profile);方法
private PropertySource<?> loadIntoGroup(String identifier, String location,
Profile profile) {
try {
return doLoadIntoGroup(identifier, location, profile);
}
catch (Exception ex) {
throw new IllegalStateException(
"Failed to load property source from location '" + location + "'",
ex);
}
}
進入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;
}
這裏存在三層for循環,location一層 name一層 ex擴展名一層,這裏休息一下,明天有空繼續
繼續回來,爲驗證讀取配置文件的順序,在resources文件下我創建了兩個配置文件application.properties 和application-dev.properties這兩個文件
第一個進入doLoadIntoGroup中resource != null && resource.exists()判斷中的代碼是application.properties
2.3.4 隨後進入propertySource = this.propertiesLoader.load(resource, group, name,(profile == null ? null : profile.getName()));
且看這個propertySource是什麼對象,爲org.springframework.core.env.PropertySource,
該類的說明大致爲 該類爲抽象封裝鍵值對的基類,其中屬性source 是泛型,可以是java.util.properties對象、java.util.map對象、servletContext和servletconfig對象等,一般這個對象不單獨使用,結合org.springframework.core.env.PropertySources類結合使用,也就是PropertySources類來承載PropertySource類
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;
}
具體解析文件的爲PropertiesPropertySourceLoader 和YamlPropertySourceLoader ,後面就不看了,最終還是以文件的方式讀出來,返回的是PropertySource的子類
比如這裏是PropertiesPropertySource,其中的source爲Properties類
最後存儲到了PropertySourcesLoader這個對象的private final MutablePropertySources propertySources;這個屬性裏面
2.3.5 進入handleProfileProperties(propertySource);方法,目前不是很清楚他的作用
private void handleProfileProperties(PropertySource<?> propertySource) {
SpringProfiles springProfiles = bindSpringProfiles(propertySource);
maybeActivateProfiles(springProfiles.getActiveProfiles());
addProfiles(springProfiles.getIncludeProfiles());
}
2.3.5 從一層方法返回出來,讀取配置文件算是完成了,接下來進入addConfigurationProperties(this.propertiesLoader.getPropertySources()); 方法
這個方法大致就是把剛纔讀取到的所有配置文件綁定到 Loader對象裏面
private final ConfigurableEnvironment environment;這個屬性上
因爲addPropertySources(environment, application.getResourceLoader());的參數是對象,即對象裏面的屬性改變也會改變,也會改變參數對象的屬性值,也就是說,到這裏,spring把剛纔讀到的配置文件信息無形之中封裝了入參environment這個對象裏面了,至此addPropertySources方法分析完畢。
3.進入configureIgnoreBeanInfo(environment);方法,方法入參爲environment
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());
}
}
這個方法就是獲取environment中關於spring.beaninfo.ignore 的配置,並把值放到System中去,默認值爲true,至於這個系統參數有何作用,現在不得而知
4.接下來進入bindToSpringApplication(environment, application); 顧名思義,將environment綁定到application
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);
}
}
這裏首先構建了一個PropertiesConfigurationFactory對象
這個類的解釋是Validate some Properties
(or optionally PropertySources
) by binding them to an object of a specified type and then optionally running a Validator
over it.
理解一下就是他可以用來校驗一些屬性或是屬性源通過綁定到一個指定類型的對象上,然後就選擇性的在這個對象上實施他們
這裏target 爲之前最早創建的SpringApplication,
targetName爲"spring.main"
propertySuorces爲environment中的propertySuorces,也就包含我們讀出來的配置文件
conversionService 爲DefaultconversionService
接下來進入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);
}
}
進入doBindPropertiesToTarget();方法,大致的意思就是將propertyValues綁定到SpringApplication這個對象上
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);
}
到這裏,postProcessEnvironment方法就結束了
總結一下,讀取配置文件,目前支持properties和yml兩種形式,默認文件名爲application加或不加active,讀取文件後封裝到springApplication中去,設置系統參數spring.beaninfo.ignore,綁定數據到springApplication對象上