Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。
Spring Boot的配置文件
初识Spring Boot时我们就知道,Spring Boot有一个全局配置文件:application.properties或application.yml。
我们的各种属性都可以在这个文件中进行配置,最常配置的比如:server.port、logging.level. 那它是如何配的呢 如何写的呢?
先从pom工程说起
pom.xml
在父工程中的核心依赖 spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点击进spring-boot-dependencies.pom
<properties>
<activemq.version>5.15.13</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.82</appengine-sdk.version>
<artemis.version>2.12.0</artemis.version>
<aspectj.version>1.9.6</aspectj.version>
<assertj.version>3.16.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
.....
我们写或者引入一些spring依赖的时候,不需要指定版本,因为有版本仓库
-
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-
spring-boot-starter:场景启动器,当我们要使用web模块的时候,它帮我们导入web模块的相关依赖,当然,依赖的版本都受父项目仲裁
-
Spring Boot将所有的功能场景抽取出来,做成一个starter,
只需在项目里引入这些starter所有的相关的依赖就会
导入进来,需要什么功能就导入什么场景的starter。
主程序
//@SpringBootApplication标注这个类是个SpringBoot应用
@SpringBootApplication
public class Springboot01Application {
public static void main(String[] args) {
//启动
SpringApplication.run(Springboot01Application.class, args);
}
}
点进@SpringBootApplication注解
标准注解我们无需关注
- @ComponentScan(): 扫描指定的包
核心注解
- @SpringBootConfiguration:SpringBoot的配置
- @EnableAutoConfiguration:自动配置
点进@SpringBootConfiguration
再点进@Configuration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
我们发现这个配置是由@Configuration配置起来的,@Configuration(Spring配置类)说明启动类就是个配置类
@Component:是一个spring组件
@EnableAutoConfiguration自动配置
作用AutoConfigurationImportSelector给容器导入一些组件,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
-
@AutoConfigurationPackage
:自动配置包 进入
@Import(AutoConfigurationPackages.Registrar.class)
导入选择器,那到底导入哪些选择器?
Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
//注册metadata元数据
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
@Import(AutoConfigurationImportSelector.class)导入自动选择配置
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
private static final String[] NO_IMPORTS = {};
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment; //环境
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;//资源加载器
private ConfigurationClassFilter configurationClassFilter;
//选择组件 选择配置的pom.xml的东西
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//加载元数据
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
核心方法
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
:获取候选的配置*
** getCandidateConfigurations()获取候选的配置**
loadFactoryNames通过传入的EnableAutoConfiguration的类,并将类名和类加载器传入loadSpringFactories这个方法,通过loadSpringFactories这个方法,获得了一个值并转换成List,这个值是什么呢?(后面我们可以看到这是一个map)
这里先简单解释一下getOrDefault,第一个参数是key,这里是factoryTypeName;第二个参数是defaultValue,这里是Collections.emptyList()。方法的意思是:如果包含key,则返回它的值,否则返回默认值。这里对应的意思就是,将EnableAutoConfiguration的类名作为key,若包含该key则返回相应的值,若不包含则返回Collections.emptyList(),也就是一个空值
loadFactoryNames
![]()
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后它们添加在容器中
按照我们框架写的找到jar 包or.springframework.boot:spring-boot-autoconfigure下的META-INF spring.factories(部分截图)
将类路径META-INF spring.factories里面配置所有EnableAutoConfiguration的值添加到容器中。
每一个这样的xxxAutoConfigruation类都是容器中的一个组件,都加入到容器中,用它们来做自动配置
每一个自动配置类进行自动配置功能
我们以HttpEncodingAutoConfiguration解释自动配置原理 ,因为它比较常见嘛
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration
-
Configuration
: 这是一个配置类,类似编写配置文件,可以给容器中添加组件 -
@EnableConfigurationProperties(ServerProperties.class)
:启用指定类ConfigurationProperties功能;将配置文件中对应的值和ServerProperties绑定起来 -
@ConditionalOnWebApplication
:底层是@Conditional注解,可以根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效,判断当前应用是否是web应用,如果是 配置类生效 -
ConditionalOnClass(CharacterEncodingFilter.class)
:判断当前项目有没有这个类,CharacterEncodingFilter是springmvc 中进行乱码解决的过滤器; -
@ConditionalOnProperty(prefix = “server.servlet.encoding”, value = “enabled”, matchIfMissing = true)
:判断配置文件是否存在配置server.servlet.encoding.enabled;
matchIfMissing = true:即使不配置,也生效
根据当前不同的条件判断决定这个配置类是否生效
当以上条件满足后,加载类HttpEncodingAutoConfiguration
//已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器,参数的值从容器中拿,EnableConfigurationProperties(ServerProperties.class)把ServerProperties加入到容器中,这边直接拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean//给容器中添加组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
//设置编码
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
根据当前不同条件判断 这个配置类是否生效
如果配置类生效;这个配置类会往容器中添加各种组件;这些组件的属性是对应的properties类中获取的,这些类里面的每个属性又是和属性文件绑定的;
通过yml配置
server:
servlet:
encoding:
enabled: true #相当于@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
charset: utf-8
force: true #是否进行强制编码
我们能配置的属性都是来源于这个功能的properties类
//能在配置文件中配置的属性都是在xxxProperties类中封装着;配置文件能配置能配什么就可以参照某个功对应的属性类
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) //从配置文件中获取指定的值和bean属性进行绑定
public class ServerProperties {
总结
SpringFactoriesLoader.loadFactoryNames 扫描所有jar包类路径下的META-INF/spring.factories文件()
作用:把扫描到文件的内容包装成Properties对象result是一个map结构,loadSpringFactories方法的返回值就是这个result,也就是说,loadSpringFactories方法的返回值,是由扫描所有jar包下的”META-INF/spring.factories”文件得到properties对象,再存进(factoryTypeName, factoryImplementationName)这若干个键值对,最后存进result并返回
重点来了! 这时,想起之前的getOrDefault了吗?loadSpringFactories方法的返回值,是由扫描所有jar包下的”META-INF/spring.factories”文件而得到的全部的键值对,而getOrDefault(Object key, V defaultValue)只选择包含key的值返回,其余则返回空值,在这里,key就是EnableAutoConfiguration的类名
xxxAutoConfigurartion:自动配置类;给容器中添加组件;
xxxProperties:封装配置文件中的相关属性;
-
SpringBoot启动会加载大量的自动配置类;
-
看我们需要的功能有没有SpringBoot默认写好的自动配置类;
-
再来看自动配置类中到底配置了哪些组件(只要用的组件有,就不需要再来配置),若没有 需要自己配置;
-
给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。就可以在配置文件中指定这些属性的值;