一般在springboot项目中,我们只需要在appcaliton.properties或者appcaliton.yml中指定相关配置,在程序中就可以直接使用,这其中的原理是如何实现呢?
首先我们看SpringBoot项目启动流程逻辑:
// 默认SpringApplication启动都会最终调用这个构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 从spring.factories文件中加载 ApplicationContextInitializer类型的类
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置 this.listeners 通过从spring.factories中读取,其中ConfigFileApplicationListener用来读取配置
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
spring.factories
中Application Listeners:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
我们看实际的run
方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
可以看到,静态的run方法实际上是实例化了一个SpringApplication,调用其run方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 得到一个SpringApplicationRunListeners,其属性listeners是通过读取spring.factories配置得到
// 为 EventPublishingRunListener,其属性EventPublishingRunListener用来实际事件处理,
// 会将SpringApplication.listeners赋值给SimpleApplicationEventMulticaster
// 当SimpleApplicationEventMulticaster广播相关事件的时候,就调用SpringApplication.listeners进行对应事件的监听处理
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建ApplicationContext,如果不是web相关,默认返回 AnnotationConfigApplicationContext
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这就是run方法的流程,我们看看主要的几个步骤:
// 得到一个SpringApplicationRunListeners,其属性listeners是通过读取spring.factories配置得到
// 为 EventPublishingRunListener,其属性EventPublishingRunListener用来实际事件处理,
// 会将SpringApplication.listeners赋值给SimpleApplicationEventMulticaster
// 当SimpleApplicationEventMulticaster广播相关事件的时候,就调用SpringApplication.listeners进行对应事件的监听处理
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
这个后续很多功能都是基于这里的listener去实现
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 默认返回 StandardEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 这里实际上就是调用EventPublishingRunListener.environmentPrepared,
// 最终是spring.factories中ApplicationListener的onApplicationEvent对应方法来处理
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
首先会创建一个ConfigurableEnvironment
:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 默认返回 StandardEnvironment,
// 并且在父类AbstractEnvironment的默认构造中会调用子类模板方法customizePropertySources
// 在StandardEnvironment.customizePropertySources中会读取虚拟机参数和系统的环境变量
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 这里实际上就是调用EventPublishingRunListener.environmentPrepared,
// 最终是spring.factories中ApplicationListener的onApplicationEvent对应方法来处理
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// 默认为true
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// 这里主要是读取命令行参数,通过SimpleCommandLineArgsParser解析,必须是 --key=value格式
configurePropertySources(environment, args);
// 读取profile,通过读取spring.profiles.active设置activeProfile
configureProfiles(environment, args);
}
然后触发listeners.environmentPrepared(environment);`` 实际读取配置文件在 ApplicationListener中
ConfigFileApplicationListener`
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
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());
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
另外,这里还需要注意的一个点是调用了
SpringbootApplicationl.oad(); 这里会装载我们在springApplication启动的时候传入的类,
// SpringbootApplicationl.java
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// 创建 BeanDefinitionLoader ,BeanDefinitionLoader中包含了:
// AnnotatedBeanDefinitionReader、XmlBeanDefinitionReader、ClassPathBeanDefinitionScanner
// 这些AnnotationConfigApplicationContext常用到的类
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// 通过loader的AnnotatedBeanDefinitionReader注册
loader.load();
}
// BeanDefinitionLoader.java
private int load(Class<?> source) {
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
load(loader);
}
if (isComponent(source)) {
this.annotatedReader.register(source);
return 1;
}
return 0;
}
会将在springApplication启动的时候传入的类注入到IOC容器中,而一般该类上都有SpringBootApplication
注解,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
..............
}
我们之前分析
spring源码解析AnnotationConfigApplicationContext启动流程分析,加载Bean信息 在这里可以看到,将带有SpringBootApplication注解的类注入到IOC容器中,能够触发后续Bean信息的载入
当执行prepareEnvironment
方法的时候,会通过EventPublishingRunListener.environmentPrepared广播该事件,对应的事件为:ApplicationEnvironmentPreparedEvent
,在ConfigFileApplicationListener
中处理该事件的时候,首先会读取spring.factories
中EnvironmentPostProcessor
:
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
同时也会将ConfigFileApplicationListener
自己加入到这个处理链中,具体处理逻辑addPropertySources
:
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
这里又通过其内部类Loader
的load方法来执行:
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
// 加载spring.factoruies中PropertySourceLoader对应的类,为PropertiesPropertySourceLoader、YamlPropertySourceLoader
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 默认返回null和default两个
initializeProfiles();
// 这里首先读取的就是默认的两个null和default,
// 如果在读取的文件中发现还有其他的profile,会将其加入到this.profiles中
// 这样,就完成了对其他profile的读取
while (!this.profiles.isEmpty()) {
// 默认先读取的是profile=null,也就是applicaiton.properties,然后读取的profile=default,也就是application-default.properties配置
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
}
// profile为空或者profile不为空且在environment的acceptsProfiles中,如果在application.properties中配置了spring.profiles配置,这里返回为false,application.properties无法加入配置
private DocumentFilter getPositiveProfileFilter(Profile profile) {
return (Document document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
}
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
};
}
// profile为空且document的profile在acceptsProfiles中
private DocumentFilter getNegativeProfileFilter(Profile profile) {
return (Document document) -> (profile == null && !ObjectUtils.isEmpty(document.getProfiles())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles())));
}
通过initializeProfiles();
来读取profile配置:
// 默认返回两个,null和default
private void initializeProfiles() {
this.profiles.add(null);
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
private Set<Profile> getProfilesActivatedViaProperty() {
if (!this.environment.containsProperty("spring.profiles.active")
&& !this.environment.containsProperty(“spring.profiles.include”)) {
return Collections.emptySet();
}
Binder binder = Binder.get(this.environment);
Set<Profile> activeProfiles = new LinkedHashSet<>();
activeProfiles.addAll(getProfiles(binder,“spring.profiles.include”));
activeProfiles.addAll(getProfiles(binder, "spring.profiles.active"));
return activeProfiles;
}
···
在类构造实例化的时候,会加载`spring.factories`中`PropertySourceLoader`类:
```properties
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
实际load方法中:
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// getSearchLocations 默认返回 DEFAULT_SEARCH_LOCATIONS 切分后的数组,即 [ classpath:/,classpath:/config/,file:./,file:./config/]
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// 这里得到默认的配置名称前缀 application
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
private Set<String> getSearchNames() {
if (this.environment.containsProperty(“spring.config.name”)) {
String property = this.environment.getProperty(“spring.config.name”);
return asResolvedSet(property, null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, "application");
}
可以看到,默认就是 classpath:/,classpath:/config/,file:./,file:./config/对这几个目录下进行文件读取,默认名称为application
,可以看到,这里读取配置名称是通过环境中是否有spring.config.name
这个配置,如果没有则读取默认的,这里在spring-cloud的配置中心的实现中会用到这个。
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// 默认name为application,这个分支跳过
if (!StringUtils.hasText(name)) {
// this.propertySourceLoaders 为 spring.factoruies中PropertySourceLoader对应的类,为PropertiesPropertySourceLoader、YamlPropertySourceLoader
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// PropertiesPropertySourceLoader可以加载 "properties", "xml" 后缀文件
// YamlPropertySourceLoader可以加载 "yml", "yaml"
// 判断路径下是否有符合propertySourceLoaders加载的后缀的文件
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
//
Set<String> processed = new HashSet<>();
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
这里通过构造时候读取的PropertySourceLoader
来读取配置,先得到各个PropertySourceLoader
能够读取的文件的格式,依次进行加载:
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
// 先加载application-profile.properties文件
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
// 加载application.properties文件
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
try {
// 先判断对应的文件是否存在,如果不存在,则跳过
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
}
return;
}
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped empty config extension ", location,
resource, profile);
this.logger.trace(description);
}
return;
}
String name = "applicationConfig: [" + location + "]";
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
profile);
this.logger.trace(description);
}
return;
}
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
if (filter.match(document)) {
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
this.logger.debug(description);
}
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property " + "source from location '" + location + "'",
ex);
}
}
这里可以看到,最后加载落在了loadDocuments
:
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
throws IOException {
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
// .properties返回 OriginTrackedMapPropertySource,list只有一个,
// 但是yaml则可能有多个,因为在yaml中可以配置多个profile,也可以通过spring.profiles指定有哪些profile
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
可以看到最后还是通过PropertySourceLoader
来进行实际读取。
// PropertiesPropertySourceLoader.java
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
Map<String, ?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
return Collections.singletonList(new OriginTrackedMapPropertySource(name, properties));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
return new OriginTrackedPropertiesLoader(resource).load();
}
最后返回的是一个OriginTrackedMapPropertySource
,然后会将获取到的OriginTrackedMapPropertySource
添加到Loader
的属性private Map
,这里就加载完了各个配置文件,接下来看addLoadedPropertySources
,通过这个将配置添加到了Env中:
MutablePropertySources destination = this.environment.getPropertySources();
// this.loaded是一个LinkedHashMap,放入的时候是application-dev.properties,application.properties
// 这里进行反转,使application.properties的配置靠前
List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
Collections.reverse(loaded);
String lastAdded = null;
Set<String> added = new HashSet<>();
for (MutablePropertySources sources : loaded) {
for (PropertySource<?> source : sources) {
if (added.add(source.getName())) {
addLoadedPropertySource(destination, lastAdded, source);
lastAdded = source.getName();
}
}
}
}
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
PropertySource<?> source) {
if (lastAdded == null) {
if (destination.contains(DEFAULT_PROPERTIES)) {
destination.addBefore(DEFAULT_PROPERTIES, source);
}
else {
destination.addLast(source);
}
}
else {
destination.addAfter(lastAdded, source);
}
}
}
命令行参数 > 虚拟机环境变量 > 系统环境变量 > application-dev.properties > application.properties