一、简介
Spring Boot 的自动配置机制是其最核心的功能之一,它使得开发者能够快速地搭建应用程序而无需过多的手动配置。下面是对 Spring Boot 自动配置原理的一个概述:
1. Starter POMs (启动器)
Spring Boot 提供了一系列的 Starter POMs,这些 POMs 包含了构建应用所需的依赖项集合。例如,spring-boot-starter-web
包含了 Web 应用所需的所有依赖。
2. Auto-configuration (自动配置)
当引入 Starter POMs 时,Spring Boot 会自动查找并启用对应的自动配置类。自动配置类通常位于 org.springframework.boot.autoconfigure
包及其子包下,并带有 @EnableAutoConfiguration
注解。
关键组件
@SpringBootApplication
:这是 Spring Boot 应用的主注解,它包含了@SpringBootConfiguration
,@EnableAutoConfiguration
和@ComponentScan
。其中@EnableAutoConfiguration
是启动自动配置的关键。@EnableAutoConfiguration
:该注解用来激活自动配置功能。@AutoConfigurationPackage
:此注解用于指定自动配置包的位置。@Import
:用于导入特定的自动配置类。
配置查找
spring.factories
:在META-INF/spring.factories
文件中定义了自动配置类的列表。例如,org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
会被添加到 Spring 应用上下文中。- 条件注解:如
@ConditionalOnClass
,@ConditionalOnMissingBean
,@ConditionalOnProperty
等,用于控制自动配置类是否生效。
3. 自动配置类
每个自动配置类都负责为特定的组件或技术栈设置默认配置。这些类通常会检查应用上下文中的 Bean 是否存在,以及环境变量和配置文件中的属性值,从而决定是否激活某些配置。
4. 默认配置和覆盖
Spring Boot 会为很多常用的框架和技术提供默认的配置,但这些配置可以被覆盖。开发者可以通过编写自己的配置类,或者在 application.properties
或 application.yml
文件中修改属性值来覆盖默认配置。
5. 自定义自动配置
如果需要为某个第三方库提供自动配置支持,可以创建自己的自动配置类,并遵循上述的模式。通常需要在 META-INF/spring.factories
文件中注册新的自动配置类。
示例
假设我们使用了 spring-boot-starter-web
,那么 Spring Boot 会自动寻找 DispatcherServlet
并配置相关的 Bean。例如,它会自动配置 Tomcat
嵌入式服务器、DispatcherServlet
以及相关组件。
这就是 Spring Boot 自动配置的基本原理。它大大简化了开发过程,使开发者能够专注于业务逻辑而不是繁琐的配置细节。
二、springboot自动配置
1.Condition
Condition 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作。
测试:
思考: SpringBoot 是如何知道要创建哪个 Bean 的?比如 SpringBoot 是如何知道要创建 RedisTemplate 的?
导入坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
ClassConditon.java
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求: 导入Jedis坐标后创建Bean
//思路:判断redis.clients.jedis.Jedis.class文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
UserConfig.java
@Configuration
public class UserConfig {
//@Conditional中的ClassCondition.class的matches方法,返回true执行以下代码,否则反之
@Bean
@Conditional(value= ClassCondition.class)
public User user(){
return new User();
}
}
User.java
public class User {
}
启动类
@SpringBootApplication
public class SpringbootCondition01Application {
public static void main(String[] args) {
//启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition01Application.class, args);
//获取Bean,redisTemplate
//情况1 没有添加坐标前,发现为空
//情况2 有添加坐标前,发现有对象
// Object redisTemplate = context.getBean("redisTemplate");
// System.out.println(redisTemplate);
/********************案例1********************/
Object user = context.getBean("user");
System.out.println(user);
}
}
1. 没有添加坐标前,发现为空,报错
2. 有添加坐标前,发现有对象
小结
自定义条件:
① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判 断,返回 boolean值 。
matches 方法两个参数:
context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
metadata:元数据对象,用于获取注解属性。
② 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解
ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
ConditionalOnMissingBean:判断环境中没有对应Bean才初始化
Bean ConditionalOnBean:判断环境中有对应Bean才初始化Bean
可以查看RedisAutoConfiguration类说明以上注解使用
2.@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载
@Import注解
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。 而@Import提供4中用法:
① 导入Bean
② 导入配置类
③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
④ 导入 ImportBeanDefinitionRegistrar 实现类。
思考 SpringBoot 工程是否可以直接获取jar包中定义的Bean?
测试:
创建两个类springboot_enable和springboot_other
其中 springboot-enable-other用于模拟其他jar包
在springboot_enable中导入spring_enable_other坐标
<!--导入坐标-->
<dependency>
<groupId>com.apesource</groupId>
<artifactId>springboot-enable_other-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
MyImportBeanDefinitionRegistrar.java
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//AnnotationMetadata注解
//BeanDefinitionRegistry向spring容器中注入
//1.获取user的definition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
//2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
registry.registerBeanDefinition("user", beanDefinition);
}
}
MyImportSelector.java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载
return new String[]{"com.apesource.domain.User", "com.apesource.domain.Student"};
}
}
UserConfig.java
@Configuration
public class UserConfig {
@Bean
public User userTest() {
return new User();
}
}
Student.java
public class Student {
}
User.java
public class User {
}
测试类
@SpringBootApplication
//@Import(User.class)
//@Import(UserConfig.class)
//@Import(MyImportSelector.class)
@Import({MyImportBeanDefinitionRegistrar.class})
//@EnableCaching
//@EnableAsync
public class SpringbootEnable03Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable03Application.class, args);
/**
* Import4中用法:
* 1. 导入Bean
* 2. 导入配置类
* 3. 导入ImportSelector的实现类
* 查看ImportSelector接口源码
* String[] selectImports(AnnotationMetadata importingClassMetadata);
* 代表将“字符串数组”中的的类,全部导入spring容器
* 4. 导入ImportBeanDefinitionRegistrar实现类
*
*/
// User user = context.getBean(User.class);
// System.out.println(user);
//
// Student student = context.getBean(Student.class);
// System.out.println(student);
User user = (User) context.getBean("user");
System.out.println(user);
}
}
3.@EnableAutoConfiguration 注解
主启动类
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}
@SpringBootApplication注解内部
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
@AutoConfigurationPackage :自动配置包
@EnableAutoConfiguration开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration 告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;
AutoConfigurationImportSelector :自动配置导入选择器,给容器中导入一些组件
AutoConfigurationImportSelector.class
↓
selectImports方法
↓
this.getAutoConfigurationEntry(annotationMetadata)方法
↓
this.getCandidateConfigurations(annotationMetadata, attributes)方法
↓
方法体:
List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass
(), this.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;
↓
在所有包名叫做autoConfiguration的包下面都有META-INF/spring.factories文件
总结原理
@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class) 来加载配置类。
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean
并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean