本文将详细探讨 Spring Boot 自动装配的原理,通过本文的学习都能更深入地理解自动装配背后的设计思想,并能在实际开发中得心应手地使用这一特性。
目录
前言
原理分析
源码跟踪
@Conditional
总结
前言
本文将详细探讨 Spring Boot 自动装配的原理,通过本文的学习都能更深入地理解自动装配背后的设计思想,并能在实际开发中得心应手地使用这一特性。
原理分析
源码跟踪
源码跟踪技巧:
在跟踪框架源码的时候,一定要抓住关键点,找到核心流程。一定不要从头到尾一行代码去看,一个方法的去研究,一定要找到关键流程,抓住关键点,先在宏观上对整个流程或者整个原理有一个认识,有精力再去研究其中的细节。
要搞清楚SpringBoot的自动配置原理,要从SpringBoot启动类上使用的核心注解
@SpringBootApplication开始分析:
在@SpringBootApplication注解中包含了:
- 元注解(不再解释)
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
我们先来看第一个注解:@SpringBootConfiguration
@SpringBootConfiguration注解上使用了@Configuration,表明SpringBoot启动类就是
一个配置类。
@Indexed注解,是用来加速应用启动的(不用关心)。
接下来再先看@ComponentScan注解:
@ComponentScan注解是用来进行组件扫描的,扫描启动类所在的包及其子包下所有被
@Component及其衍生注解声明的类。
SpringBoot启动类,之所以具备扫描包功能,就是因为包含了@ComponentScan注解。
最后我们来看看@EnableAutoConfiguration注解(自动配置核心注解):
使用@Import注解,导入了实现ImportSelector接口的实现类。
AutoConfigurationImportSelector类是ImportSelector接口的实现类。
AutoConfigurationImportSelector类中重写了ImportSelector接口的selectImports()方法:
selectImports()方法底层调用getAutoConfigurationEntry()方法,获取可自动配置的配置类信息集合
getAutoConfigurationEntry()方法通过调用getCandidateConfigurations(annotationMetadata, attributes)方法获取在配置文件中配置的所有自动配置类的集合
getCandidateConfigurations方法的功能:
获取所有基于METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imp
orts文件、META-INF/spring.factories文件中配置类的集合
METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件和META-INF/spring.factories文件这两个文件在哪里呢?
- 通常在引入的起步依赖中,都有包含以上两个文件
在前面在给大家演示自动配置的时候,我们直接在测试类当中注入了一个叫gson的bean对象,进行JSON格式转换。虽然我们没有配置bean对象,但是我们是可以直接注入使用的。原因就是因为在自动配置类当中做了自动配置。到底是在哪个自动配置类当中做的自动配置呢?我们通过搜索来查询一下。
在METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
配置文件中指定了第三方依赖Gson的配置类:GsonAutoConfiguration
第三方依赖中提供的GsonAutoConfiguration类:
在GsonAutoConfiguration类上,添加了注解@AutoConfiguration,通过查看源码,可以
明确:GsonAutoConfiguration类是一个配置。
看到这里,大家就应该明白为什么可以完成自动配置了,原理就是在配置类中定义一个@Bean标识的方法,而Spring会自动调用配置类中使用@Bean标识的方法,并把方法的返回值注册到IOC容器中。
自动配置源码小结
自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别
是:
@SpringBootConfiguration
- 声明当前类是一个配置类
@ComponentScan
- 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)
@EnableAutoConfiguration
- 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)
在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中METAINF/spring.factories、METAINF/spring/org.springframework.boot.autoconfigure.AutoConfigurat
ion.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。
当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。
最后呢给大家抛出一个问题:在METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imp
orts文件中定义的配置类非常多,而且每个配置类中又可以定义很多的bean,那这些bean都会注册到Spring的IOC容器中吗?
答案:并不是。 在声明bean对象时,上面有加一个以@Conditional开头的注解,这种注解的
作用就是按照条件进行装配,只有满足条件之后,才会将bean注册到Spring的IOC容器中(下面会详细来讲解)
@Conditional
在跟踪SpringBoot自动配置的源码的时候,在自动配置类声明bean的时候,除了在方法上加了一
个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。以
Conditional开头的这些注解都是条件装配的注解。下面我们就来介绍下条件装配注解。
@Conditional注解:
作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容
器中。
位置:方法、类
@Conditional本身是一个父注解,派生出大量的子注解:
- @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
- @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册
bean到IOC容器。 - @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
下面我们通过代码来演示下Conditional注解的使用:
@ConditionalOnClass注解
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnClass(name="io.jsonwebtoken.Jwts")//环境中存在指定的
这个类,才会将该bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
pom.xml
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
测试类
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}
//省略其他代码...
}
因为io.jsonwebtoken.Jwts字节码文件在启动SpringBoot程序时已存在,所以创建
HeaderParser对象并注册到IOC容器中。
@ConditionalOnMissingBean注解
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean(name="deptController2")//不存在指定名称
的bean,才会将该bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
因为在SpringBoot环境中不存在名字叫deptController2的bean对象,所以创建
HeaderParser对象并注册到IOC容器中。
再次修改@ConditionalOnMissingBean注解:
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnMissingBean(HeaderConfig.class)//不存在指定类型的
bean,才会将bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
//省略其他代码...
}
@SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}
//省略其他代码...
}
因为HeaderConfig类中添加@Configuration注解,而@Configuration注解中包含了
@Component,所以SpringBoot启动时会创建HeaderConfig类对象,并注册到IOC容器中。
当IOC容器中有HeaderConfig类型的bean存在时,不会把创建HeaderParser对象注册到IOC
容器中。而IOC容器中没有HeaderParser类型的对象时,通过
getBean(HeaderParser.class)方法获取bean对象时,引发异常:
NoSuchBeanDefinitionException
@ConditionalOnProperty注解(这个注解和配置文件当中配置的属性有关系)
先在application.yml配置文件中添加如下的键值
name: itheima
在声明bean的时候就可以指定一个条件@ConditionalOnProperty
@Configuration
public class HeaderConfig {
@Bean
@ConditionalOnProperty(name ="name",havingValue = "itheima")//配
置文件中存在指定属性名与值,才会将bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
修改@ConditionalOnProperty注解: havingValue的值修改为"itheima2"
@Bean
@ConditionalOnProperty(name ="name",havingValue = "itheima2")//配置文
件中存在指定属性名与值,才会将bean加入IOC容器
public HeaderParser headerParser(){
return new HeaderParser();
}
因为application.yml配置文件中,不存在: name: itheima2,所以HeaderParser对象
在IOC容器中不存在
我们再回头看看之前讲解SpringBoot源码时提到的一个配置类:GsonAutoConfiguration
最后再给大家梳理一下自动配置原理:
自动配置的核心就在@SpringBootApplication注解上,SpringBootApplication这个注解
底层包含了3个注解,分别是:
- @SpringBootConfiguration
- @ComponentScan
- @EnableAutoConfiguration
@EnableAutoConfiguration这个注解才是自动配置的核心。
它封装了一个@Import注解,Import注解里面指定了一个ImportSelector接口的实现
类。
在这个实现类中,重写了ImportSelector接口中的selectImports()方法。
而selectImports()方法中会去读取两份配置文件,并将配置文件中定义的配置类做为
selectImports()方法的返回值返回,返回值代表的就是需要将哪些类交给Spring的IOC
容器进行管理。
那么所有自动配置类的中声明的bean都会加载到Spring的IOC容器中吗? 其实并不会,因
为这些配置类中在声明bean时,通常都会添加@Conditional开头的注解,这个注解就是进
行条件装配。而Spring会根据Conditional注解有选择性的进行bean的创建。
@Enable 开头的注解底层,它就封装了一个注解 import 注解,它里面指定了一个类,是
ImportSelector 接口的实现类。在实现类当中,我们需要去实现 ImportSelector
接口当中的一个方法 selectImports 这个方法。这个方法的返回值代表的就是我需要将
哪些类交给 spring 的 IOC容器进行管理。
此时它会去读取两份配置文件,一份儿是 spring.factories,另外一份儿是autoConfiguration.imports。而在 autoConfiguration.imports 这份儿文件
当中,它就会去配置大量的自动配置的类。
而前面我们也提到过这些所有的自动配置类当中,所有的 bean都会加载到 spring 的
IOC 容器当中吗?其实并不会,因为这些配置类当中,在声明 bean 的时候,通常会加上
这么一类@Conditional 开头的注解。这个注解就是进行条件装配。所以SpringBoot非
常的智能,它会根据 @Conditional 注解来进行条件装配。只有条件成立,它才会声明这
个bean,才会将这个 bean 交给 IOC 容器管理。
总结
总的来说,Spring Boot 的自动装配不仅是一项技术特性,更是对开发者生产力的提升。通过深入理解自动装配的原理与机制,我们可以更好地控制框架的行为,并根据项目需求灵活地进行定制和优化。希望本文的内容能对你在 Spring Boot 开发过程中有所帮助。