配置文件加载顺序
SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置
-
命令行参数
所有的配置都可以在命令行上进行指定
java -jar xxx.jar --server.port=8087 --server.context-path=/abcCopy to clipboardErrorCopied
多个配置用空格分开; --配置项=值
-
来自java:comp/env的JNDI属性 ⤴️
-
Java系统属性(System.getProperties()) ⤴️
-
操作系统环境变量 ⤴️
-
RandomValuePropertySource配置的random.*属性值 ⤴️
由jar包外向jar包内进行寻找;
优先加载带profile
-
jar包外部的
application-{profile}.properties
或application.yml
(带spring.profile)配置文件 ⤴️ -
jar包内部的
application-{profile}.properties
或application.yml
(带spring.profile)配置文件 ⤴️
再来加载不带profile
-
jar包外部的
application.properties
或application.yml
(不带spring.profile)配置文件 ⤴️ -
jar包内部的
application.properties
或application.yml
(不带spring.profile)配置文件 ⤴️ -
@Configuration注解类上的@PropertySource ⤴️
-
通过SpringApplication.setDefaultProperties指定的默认属性 ⤴️
所有支持的配置加载来源:
自动配置的执行流程
- @SpringBootApplication
1. @SpringBootApplication // 由启动类的@SpringBootApplication开启自动配置
2. @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //标至该类是一个配置类,与@Configuration作用一致
@EnableAutoConfiguration //启动
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
注意:@Configuration注解的配置类有如下要求:
- @Configuration不可以是final类型;
- @Configuration不可以是匿名类;
- 嵌套的configuration必须是静态类。
一、用@Configuration加载spring
1.1、@Configuration配置spring并启动spring容器
1.2、@Configuration启动容器+@Bean注册Bean
1.3、@Configuration启动容器+@Component注册Bean
1.4、使用 AnnotationConfigApplicationContext 注册 AppContext 类的两种方法1.5、配置Web应用程序(web.xml中配置AnnotationConfigApplicationContext)
二、组合多个配置类
2.1、在@configuration中引入spring的xml配置文件
2.2、在@configuration中引入其它注解配置
2.3、@configuration嵌套(嵌套的Configuration必须是静态类)
三、@EnableXXX注解
四、@Profile逻辑组配置
五、使用外部变量
- EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。
@Import(AutoConfigurationImportSelector.class) //@EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关。
public @interface EnableAutoConfiguration {
}
boot.autoconfigure.EnableAutoConfiguration注解
-> @Import了一个AutoConfigurationImportSelector实例
-> AutoConfigurationImportSelector类(implement ImportSelector),实现了selectImports() 方法,用来筛选被@Import的Configuration类(减去exclude等)
- AutoConfigurationImportSelector.class
public class AutoConfigurationImportSelector implementsDeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//......
@Override
publicString[] selectImports(AnnotationMetadata annotationMetadata) {
// 如果AutoConfiguration没开,返回{}
if(!isEnabled(annotationMetadata)) {
returnNO_IMPORTS;
}
// 将spring-autoconfigure-metadata.properties的键值对配置载入到PropertiesAutoConfigurationMetadata对象中并返回
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 基于各种配置计算需要import的configuration和exclusion
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// 判断AudoConfiguration是否开启
protectedbooleanisEnabled(AnnotationMetadata metadata) {
if(getClass() == AutoConfigurationImportSelector.class) {
// 如果配置文件中有"spring.boot.enableautoconfiguration",返回该字段的值;否则返回true
returngetEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
returntrue;
}
protectedAutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if(!isEnabled(annotationMetadata)) {
returnEMPTY_ENTRY;
}
// 获取注解的属性值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从META-INF/spring.factories文件中获取EnableAutoConfiguration所对应的configurations,但并不实例化,还要筛选
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重,List转Set再转List
configurations = removeDuplicates(configurations);
// 从注解的exclude/excludeName属性中获取排除项
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 对于不属于AutoConfiguration的exclude报错
checkExcludedClasses(configurations, exclusions);
// 从configurations去除exclusions
configurations.removeAll(exclusions);
// 所有AutoConfigurationImportFilter类实例化,并再进行一次筛选
configurations = filter(configurations, autoConfigurationMetadata);
// 实例化剩下configuration中的类,并把AutoConfigurationImportEvent绑定在所有AutoConfigurationImportListener子类实例上,当fireAutoConfigurationImportEvents事件被触发时,打印出已经注册到spring上下文中的@Configuration注解的类,打印出被阻止注册到spring
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回(configurations, exclusions)组
return newAutoConfigurationEntry(configurations, exclusions);
}
// ......
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class;
// getBeanClassLoader()这里使用的是AppClassLoader。
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
- getCandidateConfigurations方法中,SpringFactoriesLoader.loadFactoryNames(),扫描所有jar包类路径下
META-INF/spring.factories
,并对相应的key值进行筛选,这里使用的key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration。 - 把扫描到的这些文件的内容包装成properties对象,以 Properties 类型(即 key-value 形式)配置,就可以将相应的实现类注入 Spirng 容器中(key为factory类型)。从properties中获取到EnableAutoConfiguration.class(类名)对应的值,然后把它们添加在容器中
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// loadSpringFactories方法是获取所有的springFactories
// getOrDefault是通过key,获取到对应的类的集合。因为value是通过逗号相隔的,可以有多个,所以是list
// getOrDefault如果存在就返回,如果不存在那么就返回给的默认值
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 三目表达式,判断参数classLoader是否为空,如果不为空,那么直接使用传入的classLoader获取META-INF/spring.factories
// 如果为空,那么就使用系统的classLoader来获取META-INF/spring.factories
// 总之健壮性比较强,
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
// 通过循环遍历所有的META-INF/spring.factories
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 找到的每个 META-INF/spring.factories 文件都是一个 Properties 文件,将其内容加载到一个 Properties 对象然后处理其中的每个属性
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取工厂类名称(接口或者抽象类的全限定名)
String factoryTypeName = ((String) entry.getKey()).trim();
// 将逗号分割的属性值逐个取出,然后放到 结果result 中去
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
// 筛选出的结果集Map放入内存中,
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
- 在getConfigurationClassFilter与fireAutoConfigurationImportEvents方法中将其通过SpringFactoriesLoader 中的loadFactories反射对所有的配置进行筛选,实例化,并绑定到AutoConfigurationImportListener子类实例上
https://blog.csdn.net/qq_42154259/article/details/107600734
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
/**
* 通过classLoader从各个jar包的classpath下面的META-INF/spring.factories加载并解析其key-value值,然后创建其给定类型的工厂实现
*
* 返回的工厂通过AnnotationAwareOrderComparator进行排序过的。
* AnnotationAwareOrderComparator就是通过@Order注解上面的值进行排序的,值越高,则排的越靠后
*
* 如果需要自定义实例化策略,请使用loadFactoryNames方法获取所有注册工厂名称。
*
* @param factoryType 接口或者抽象类的Class对象
* @param classLoader 用于加载的类加载器(可以是null,如果是null,则使用默认值)
* @throws IllegalArgumentException 如果无法加载任何工厂实现类,或者在实例化任何工厂时发生错误,则会抛出IllegalArgumentException异常
*/
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
// 首先断言,传入的接口或者抽象类的Class对象不能为空
Assert.notNull(factoryType, "'factoryType' must not be null");
// 将传入的classLoader赋值给classLoaderToUse
// 判断classLoaderToUse是否为空,如果为空,则使用默认的SpringFactoriesLoader的classLoader
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 加载所有的META-INF/spring.factories并解析,获取其配置的factoryNames
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
// 通过反射对所有的配置进行实例化。
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
// 实例化工厂,根据
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
try {
// 根据全限定类名通过反射获取Class对象
Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
// 判断获取的Class对象是否从factoryType里面来,
// 说具体点就是判断我们配置的spring.factories中的权限定类名所对应的类是否是对应的子类或者实现类
// 比如
// org.springframework.context.ApplicationContextInitializer=
// org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,
// org.springframework.boot.context.ContextIdApplicationContextInitializer,
// 那么他就会验证ConfigurationWarningsApplicationContextInitializer和ContextIdApplicationContextInitializer是否是ApplicationContextInitializer的子类
// isAssignableFrom()方法与instanceof关键字的区别总结为以下两个点:
// isAssignableFrom()方法是从类继承的角度去判断,instanceof关键字是从实例继承的角度去判断。
// isAssignableFrom()方法是判断是否为某个类的父类,instanceof关键字是判断是否某个类的子类。
// 如果不是,则会保存
if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
throw new IllegalArgumentException(
"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
}
// 通过反射的有参构造函数进行实例化:如果直接newInstance的话,那么只能通过空参构造函数进行实例化。
// 通过这种方式可以通过不同参数的构造函数进行创建实例,但是这里并没有传入参数,所以调用的是默认空惨构造函数
return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
ex);
}
}
-
可见selectImports()是AutoConfigurationImportSelector的核心函数,其核心功能就是获取spring.factories中EnableAutoConfiguration所对应的Configuration类列表,由@EnableAutoConfiguration注解中的exclude/excludeName参数筛选一遍,再由AutoConfigurationImportFilter类所有实例筛选一遍,得到最终的用于Import的configuration和exclusion。
-
该函数是被谁调用的呢?在org.springframework.context.annotation.ConfigurationClassParser类中被processImports()调用,而processImports()函数被doProcessConfigurationClass()调用。下面从doProcessConfigurationClass() 看起。
-
上面为@Import()的Spring的注释的底层实现
-
接着看我们EnableAutoConfiguration.class(类名)对应的值:
-
每一个这样的
xxxAutoConfiguration
类都是容器中的一个组件,都加入到容器中;用他们来做自动配置; -
每一个自动配置类进行自动配置功能;
-
eg :HttpEncodingAutoConfiguration
package org.springframework.boot.autoconfigure.web.servlet;
......
//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(
proxyBeanMethods = false
)
/**
* 启动指定类的ConfigurationProperties功能;
* 将配置文件中对应的值和HttpProperties绑定起来;
* 并把HttpProperties加入到ioc容器中
*/
@EnableConfigurationProperties({HttpProperties.class})
/**
* Spring底层@Conditional注解
* 根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;
* 判断当前应用是否是web应用,如果是,当前配置类生效
*/
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类
@ConditionalOnClass({CharacterEncodingFilter.class})
/**
* 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的
* 即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
*/
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//它已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean //判断容器有没有这个组件?(容器中没有才会添加这个组件)
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
......
- 根据当前不同的条件判断,决定这个配置类是否生效
- 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
- 相应的配置类在@EnableConfigurationProperties中指定,其会在读取我们写的properties并将值注入类中,由
xxxAutoConfiguration
读取 实例化的xxxxProperties类,根据属性生成相应的bean给ApplicationContext调用。
所有在配置文件中能配置的属性都是在xxxxProperties
类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类
@ConfigurationProperties(
prefix = "spring.http"
)
public class HttpProperties {
private boolean logRequestDetails;
private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();
我们配置时的流程:
SpringBoot启动会加载大量的自动配置类
我们看我们需要的功能有没有SpringBoot默认写好的自动配置类
再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值
xxxxAutoConfigurartion
:自动配置类;
xxxxProperties
:封装配置文件中相关属性;
Apllication总体流程
SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:
1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:
- 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
- 推断并设置main方法的定义类。
2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
3) 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。
4) 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。
5) 如果SpringApplication的showBanner属性被设置为true,则打印banner。
6) 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。
7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。
8) 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。
9) 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
10) 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。
11) 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
12) 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。
13) 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)****