一、Spring Boot 自动配置相关注解说明
为了能让同学们后面阅读的时候更加专注于代码实现原理,而不是去纠结每个注解的作用,这里将Spring Boot 常用的注解进行说明,方便后面的阅读。
(1) @Value 注解
作用:
将外部的值动态注入到Bean中
方式一:注入普通字符串
@Component
public class St_annotation_value {
// 等价与 <bean class="St_annotation_value"> <property name ="name" value="xiao wang"></property></bean>
@Value("xiao wang")
private String name;
// getting and setting..
}
测试下
@SpringBootTest
class StAutoconfigApplicationTests {
@Autowired
St_annotation_value value;
@Test
void contextLoads() {
// 打印结果 xiao wang
System.out.println(value.getName());
}
}
通过 PlaceHolder方式注入 @Value(${property:default value})
1.先在 application.properties加入
name=xiao wang
- 代码修改为
@Component
public class St_annotation_value {
//@Value("${key}") 格式
@Value("${name}")
private String name;
}
- 打印结果
xiao wang
(2) @Import注解
作用:
通过快速导入的方式实现把实例加入spring的IOC容器中
格式:
@Import({ 类名.class , 类名.class... })
方式一:配合 spring bean 容器使用将类对象注册为 bean对象
@Import({Person.class}) // 将 Person 对象注册为bean 对象
@Configuration // 这里也可以使用 @Component 只要是当前类是bean对象即可
public class St_annotation_import {
}
测试一下
@SpringBootTest
class StAutoconfigApplicationTests {
@Autowired
Person person;
@Test
void contextLoads() {
//成功打印hello,说明person对象已注册为bean 对象
person.hello();
}
}
方式二:配合importSelector使用,批量将指定的类注册为bean
- 自定义一个 ImportSelector类,这里说明下ImportSelector类,当引用到ImportSelector 会调用 ImportSelector的 selectImports,将此方法返回的类 都注册为bean对象。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回需要注册为bean的 类全限名
return new String[]{"com.ym.st_autoconfig.entity.Bird"};
}
}
- @Import 配合ImportSelector 使用。
@Import({MyImportSelector.class})
@Configuration
public class St_annotation_import {
}
- 进行测试
@SpringBootTest
class StAutoconfigApplicationTests {
@Autowired
Bird bird;
@Test
void contextLoads() {
// 调用成功,说明bird类已经注册为bean对象
bird.say();
}
}
(3) @ConfigurationProperties注解
作用:
当一个类有多个属性的话,使用@Value一个个注入是不是太麻烦了,这时候可以使用@Configuration注解,他支持批量注入属性,该注解有一个prefix属性,通过指定的前缀,绑定配置文件中的配置,该注解可以放在类上,也可以放在方法上
使用:
- 在 application.properties加入
person.name=xiao hong
person.age=12
person.sex=男
- 在需要注入属性的类中加入注解
@Component
// 通过前缀去找对应属性,注入到bean中
@ConfigurationProperties(value = "person")
public class St_annotation_configurationProperties {
private String name;
private int age;
private String sex;
// getting and setting..
}
- 测试一下
@SpringBootTest
class StAutoconfigApplicationTests {
@Autowired
St_annotation_configurationProperties configurationProperties;
@Test
void contextLoads() {
System.out.println(configurationProperties.toString());
// 打印结果 St_annotation_configurationProperties{name='xiao hong', age=12, sex='男'}
}
}
(4) @Configuration注解
作用:
一个类级别的注释,指示对象是bean定义的来源,通俗的解释就是对bean对象进行配置的地方,相当于我们以前xml配置。
使用:
@Configuration
public class St_annotation_configuration {
//相当于 我们以前 xml 定义的 <bean id='cat' class='com.ym.st_autoconfig.entity.Cat'/>
@Bean
public Cat getCat(){
return new Cat();
}
}
(5) @Conditional注解
作用:
根据某个条件创建特定的Bean,通过实现Condition接口,并重写matches接口来构造判断条件,就是根据条件判断是否需要创建注册这个bean
使用:
- 自定义conditional
public class MyConditional implements Condition {
// 会调用matches() 判断是否将此类注册为bean ,true 注册 false 拒绝注册
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
}
}
- 使用 conditional
@Configuration
@Conditional(MyConditional.class)
public class St_annotation_conditional {
@Bean
public Tiger getTiger(){
return new Tiger();
}
}
- 测试一下
@SpringBootTest
class StAutoconfigApplicationTests {
@Autowired
Tiger tiger;
@Test
void contextLoads() {
// 调用失败,因为我们前面返回的总是false所以@configuration 没有生效,当改为true后,程序运行成功
tiger.say();
}
}
常用的conditional
扩展注解 | 作用 |
ConditionalOnBean | 容器中存在指定 Bean,则生效。 |
ConditionalOnMissingBean | 容器中不存在指定 Bean,则生效。 |
ConditionalOnClass | 系统中有指定的类,则生效。 |
ConditionalOnMissingClass | 系统中没有指定的类,则生效。 |
ConditionalOnProperty | 系统中指定的属性是否有指定的值。 |
ConditionalOnWebApplication | 当前是web环境,则生效。 |
二、Spring Boot 自动配置原理分析
流程概述
先大致讲解下Spring Boot 自动配置的流程,这样结合流程在接下去看分析流程,可能会更加清晰一下。
1. 首先启动类上使用了 @EnableAutoCnnfiguration注解,
2. 这个注解里应用了一个@import注解,这注解又引用了自定义的 ImportSelector,
3. 自定义的ImportSelector 会去找到 META-INF/spring.factories配置文件中所有的自定配置类。
4. 然后根据@conditional注解决定是否加载这些配置类。
5. 所有配置类加载出来来通过@ConfigurationProperties 去配置文件中读取配置参数,如果没有则取默认的参数。
6. 最后将读取到的配置类都加载为bean。
详细分析
首先方法的入口肯定是SpringApplication.run() , 然后会调用refresh() 初始化上下文,最后调用 ConfigurationClassPostProcessor (BeanFactory 后置处理器) 对 配置类进行解析,这时候我们的启动类中应用了 @SpringBootApplication 这个组合注解,其中包含 @Configuration,所以会对这个启动类的注解进行解析处理。
然后我们看看 @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) })
启动 @EnableAutoConfiguraion 字面意思来看应该就是自动配置的关键了,我们点进去看看, 启动 @AutoConfigurationPackage 是自动扫描包的范围, @Import 导入了 自定义的AutoConfigurationImportSelector,之前讲过 ImportSelector 的作用是将需要注册bean返回,那么我们看看AutoConfigurationImportSelector到底返回了哪些类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//..........
}
AutoConfigurationImportSelector 的 selectImports 方法 ,调用了 getAutoConfigurationEntry方法,最后的调用链是 selectImports -> getAutoConfigurationEntry -> getCandidateConfigurations,那我们看看 getCandidateConfigurations方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
getCandidateConfigurations 方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass() 返回的是 EnableAutoConfiguration.class
// SpringFactoriesLoader.loadFactoryNames 就是已 EnableAutoConfiguration类的全限名为key 去 META-INF/spring.factories 下面找需要的加载的类
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;
}
在spring.factories文件中就是这样的格式
那么到这里会想一个问题了 EnableAutoConfiguration 的类那么多怎么来确认他需不需要加载呢? 这就用到了我们上面的说的 @Conditional了 每个配置类都有引用这个注解来判断是否去要加载,在getAutoConfigurationEntry方法中有调用
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);
}
到这里springboot 怎么自动去加载配置的过程差不多结束了,那么还有一个问题,现在许多都有默认配置 我们怎么去覆盖这些配置或者说有哪些可以配置的呢?这就要去看 具体的配置类了 这里已 ServletWebServerFactoryAutoConfiguration为例,点击去我们可以看到
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
重点在 @EnableConfigurationProperties 这个注解,这个注解作用是使用 @ConfigurationProperties 注解的类生效,我们点进ServerProperties
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
@NestedConfigurationProperty
private final ErrorProperties error = new ErrorProperties();
//..........
}
看到它引用了ConfigurationProperties注解,并且前缀是 server,那么我们定义属性的时候 只要 前缀是server 并且ServerProperties有这个属性就可以了。
最后总结
SpringBoot启动的时候会通过@EnableAutoConfiguration加载META-INF/spring.factories配置文件中的所有配置,并进行加载,这些配置的属性是通过 @ConfigurationProperties注解进行全局加载的
三、自定义Spring Boot 自动配置类
上面说了这么多还是要动手实践下才会印象深刻,那么结合上面所说的,如果我们要自定义一个自动配置类 那么我们需要什么?
- 在 META-INF就建立一个 spring.factories配置文件
- 在spring.factories 中配置上自己的自动配置类
- 定义一个自动配置的属性类
定义一个配置文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ym.st_autoconfig.configuration.MyAutoConfiguration
定义一个配置类
@ConfigurationProperties("my")
public class MyProperties {
private String name;
private int age;
}
定义一个自动配置类
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
public MyAutoConfiguration(MyProperties myProperties){
System.out.println(myProperties.toString());
}
}
配置文件中增加配置
my.name=xiao hong
my.age=12
启动后观察控制台,启动成功
好了,springboot 自动配置原理就分析到了这里,如果有问题欢迎指出!!!谢谢大家观看