自动配置原理
Spring Boot自动配置是Spring Boot框架的一个核心特性,Spring Boot利用“约定优于配置”的原则,能够根据项目中添加的依赖、配置文件和类路径下的类自动配置Spring应用程序,极大地简化开发者的配置工作,提高开发效率。
其所有的配置类都是由 spring-boot-autoconfiguration这个模块提供的,自动配置类一般是以 xxxAutoConfiguration命名,自动配置类需要注册到自动配置文件中。
SpringBoot自动配置是指在SpringBoot应用启动时,将一些配置类自动注入到Spring的IOC容器中,项目运行时直接使用这些配置类的属性。简单来说就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使项目快速运行。
关键步骤
1. 启动类的定义:
创建一个带有@SpringBootApplication注解的启动类,该注解是一个组合注解,它包含了@Configuration、@EnableAutoConfiguration和@ComponentScan。
2. 启用自动配置:
@EnableAutoConfiguration注解是自动配置的入口。它告诉Spring Boot根据类路径下的类、Bean定义以及各种属性设置来尝试猜测并配置你想要的Spring应用程序配置。
3. 条件注解的运用:
自动配置是通过一系列的条件注解来实现的,这些注解确保了只有在特定条件满足时,相关的配置才会生效。常见的条件注解包括:
@ConditionalOnClass:当类路径下有指定的类时。
@ConditionalOnMissingBean:当容器中没有指定Bean时。
@ConditionalOnProperty:当指定的配置属性满足特定值时。
@ConditionalOnResource:当类路径下有指定的资源文件时。
4.配置属性的绑定:
Spring Boot自动配置会读取application.properties或application.yml文件中的属性,并将这些属性值绑定到相应的Bean上。这是通过@Value注解或者更高级的@ConfigurationProperties注解来实现的。
5. 依赖管理:
Spring Boot通过提供一系列的“Starters”来简化依赖管理。Starters是一组方便的依赖描述符,你可以将它们添加到你的项目中以获取所需的所有Spring和相关技术的一站式服务。
一、Condition注解
Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操 作。
@Conditional注解是一个核心的元注解,用于创建条件化的Bean定义。这意味着只有当指定的条件得到满足时,相关的Bean才会被创建和注册到Spring容器中。Spring Boot利用了这个特性来实现自动配置,使得配置更加灵活和智能。
以下是一些常用的条件注解及其用途:
@Conditional:
这是一个基础的条件注解,需要一个Condition实现类作为参数。只有当Condition实现类的matches方法返回true时,被注解的Bean才会被创建。
@ConditionalOnBean:
当Spring容器中存在至少一个指定类型的Bean时,条件成立。
@ConditionalOnMissingBean:
当Spring容器中不存在指定类型的Bean时,条件成立。这在自动配置中很常见,用于确保只有当用户没有定义自己的Bean时,默认的Bean才会被创建。
@ConditionalOnClass:
当类路径下存在指定的类时,条件成立。
@ConditionalOnMissingClass:
当类路径下不存在指定的类时,条件成立。
@ConditionalOnProperty:
当指定的配置属性存在并且满足条件时,条件成立。
@ConditionalOnResource:
当类路径下存在指定的资源文件时,条件成立
@ConditionalOnWebApplication:
当应用程序是一个Web应用程序时,条件成立。
@ConditionalOnNotWebApplication:
当应用程序不是一个Web应用程序时,条件成立。
这些条件注解使得Spring Boot的自动配置能够根据当前的应用程序环境、类路径、配置属性等因素动态地决定哪些Bean应该被创建。
思考:SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate 的?
Spring Boot确定要创建哪个Bean的过程涉及以下几个关键步骤:
类路径检查: Spring Boot在启动时会检查项目的类路径(Classpath)。如果类路径下存在特定的库(例如spring-boot-starter-data-redis),Spring Boot会认为你可能想要使用这个库,并尝试为其提供自动配置。
自动配置类: Spring Boot自动配置是通过一系列的自动配置类来实现的。这些类通常位于spring-boot-autoconfigure依赖中。每个自动配置类都会针对特定的功能或第三方库进行配置。例如,RedisAutoConfiguration是负责配置Redis相关的Bean。
条件注解: 自动配置类通常会使用条件注解来确保只有在满足特定条件时才会生效。例如,@ConditionalOnClass注解用来检查类路径中是否存在某个类(在这个例子中是RedisOperations和RedisTemplate),如果存在,则相关的配置会被激活。
以下是一个简化的例子,展示了Spring Boot如何知道要创建RedisTemplate:
@Configuration
@ConditionalOnClass({RedisOperations.class, RedisTemplate.class})
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// ... 其他配置
return template;
}
// ... 可能还有其他Bean的定义
}
@ConditionalOnClass({RedisOperations.class, RedisTemplate.class})确保只有当RedisOperations和RedisTemplate类都在类路径中时,RedisAutoConfiguration类才会被加载。
@EnableConfigurationProperties(RedisProperties.class)启用了对RedisProperties类的配置属性绑定,这样就可以从application.properties或application.yml文件中读取Redis的配置。
@Bean注解标记了一个方法,这个方法会创建并返回一个Bean。在这个例子中,redisTemplate方法会创建一个RedisTemplate的实例。
@ConditionalOnMissingBean(name = "redisTemplate")确保只有当Spring容器中不存在名为redisTemplate的Bean时,redisTemplate方法才会被执行。
因此,当你的项目中包含了spring-boot-starter-data-redis依赖,并且没有手动定义一个名为redisTemplate的Bean时,Spring Boot的自动配置机制就会自动创建一个RedisTemplate的Bean。
案例:需求1 在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
1. 导入Jedis坐标后,加载该Bean,没导入,则不加载。
导入jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
创建ClassConditional类实现Condition接口,重写matches方法
public class ClassCondition implements Condition {
@Override
//判断import redis.clients.jedis.Jedis文件是否存在、存在--导入
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean flag=true;
try {
Class<?> aClass = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag=false;
}
return flag;
}
}
加入注解
@Configurable
public class UserConfig {
@Bean
@Conditional(ClassCondition.class)
public User user(){
return new User();
}
}
分别直接运行和注释掉jedis后运行
案例:需求2 在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定
创建实体类
public class User {
//。。。。。。。
}
创建注解
@Target({ElementType.TYPE, ElementType.METHOD})//可以修饰在类与方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效节点runtime
@Documented//生成文档
@Conditional(value = ClassCondition.class)
public @interface conditiononmy {
String[] value();//设置此注解的属性redis.clients.jedis.Jedis
}
创建ClassConditional类实现Condition接口,重写matches方法
public class ClassCondition implements Condition {
//2.需求: 导入通过注解属性值value指定坐标后创建Bean
//获取注解属性值 value
Map<String, Object> map = metadata.getAnnotationAttributes(conditiononmy.class.getName());
System.out.println(map);
String[] value = (String[]) map.get("value");
boolean flag = true;
try {
for (String s : value) {
Class<?> cls = Class.forName(s);
}
flag = true;
} catch (Exception e) {
flag = false;
}
return flag;
}
}
加入注解
@Configuration
public class Userconfig {
//@Conditional中的ClassCondition.class的matches方法,返回true执行以下代码,否则反之
@Bean
@conditiononmy(value = {"com.alibaba.fastjson.JSON","redis.clients.jedis.Jedis"})
public User user(){
return new User();
}
}
测试(启动类)
@SpringBootApplication
public class SpringbootCondition01Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition01Application.class, args);
//固定坐标在condition输入,使用原有注解实现
Object user = context.getBean("user");
System.out.println(user);
//不存在坐标:NoSuchBeanDefinitionException: No bean named 'user' available
//存在坐标:com.example.springboot_condition_01.domain.User@726a6b94
//动态输入坐标,在config配置类使用自定义注解@ConditiononClass(坐标数组)输入,使用自定义注解实现
Object user1 = context.getBean("user1");
System.out.println(user1);
Object user2 = context.getBean("user2");
System.out.println(user2);
}
}
实现步骤:
不使用@Conditional(ClassCondition.class)注解
自定义注解@ConditionOnClass,因为他和之前@Conditional注解功能一直,所以直接复制
编写ClassCondition中的matches方法
Condition – 小结
自定义条件:
① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判 断,返回
boolean值 。 matches 方法两个参数:
• context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
• metadata:元数据对象,用于获取注解属性。
② 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解
SpringBoot 提供的常用条件注解:
一下注解在springBoot-autoconfigure的condition包下
ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean ConditionalOnBean:判断环境中有对应Bean才初始化Bean
可以查看RedisAutoConfiguration类说明以上注解使用
距离演示ConditionalOnProperty
二.@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现Bean的动态加载
思考 SpringBoot 工程是否可以直接获取jar包中定义的Bean?
@Import注解
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。 而@Import提供4中用法:
① 导入Bean
直接写好实体类,使用@Import注解导入即可
@SpringBootApplication
@Import(User.class)
public class Enable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Enable01Application.class, args);
//获取Bean
User user = context.getBean(User.class);
System.out.println(user);
}
}
② 导入配置类
配置类
@Configuration
public class UserConfig {
@Bean
public User user(){
return new User();
}
}
导入
@SpringBootApplication
@Import(UserConfig.class)
public class Enable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Enable01Application.class, args);
//获取Bean
User user = context.getBean(User.class);
System.out.println(user);
}
}
③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载
return new String[]{"com.wei.enable02.domain.User", "com.wei.enable02.domain.Student"};
}
}
@SpringBootApplication
@Import(MyImportSelector.class)
public class Enable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Enable01Application.class, args);
//获取Bean
User user = context.getBean(User.class);
System.out.println(user);
}
}
④ 导入 ImportBeanDefinitionRegistrar 实现类。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1.获取user的definition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
//2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
registry.registerBeanDefinition("user", beanDefinition);
}
}
@SpringBootApplication
@Import(MyImportBeanDefinitionRegistrar.class)
public class Enable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Enable01Application.class, args);
//获取Bean
User user = context.getBean(User.class);
System.out.println(user);
}
}
三.@EnableAutoConfiguration 注解
主启动类
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}
@SpringBootApplication注解内部
@SpringBootApplication 是一个复合注解,大概就可以把 @SpringBootApplication 看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ......
}
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。扫描包下的类中添加了@Component (@Service,@Controller,@Repostory,@RestController)注解的类 ,并添加的到spring的容器中,可以自定义不扫描某些 bean。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
//@SpringBootConfiguration注解内部
//这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
@Configuration
public @interface SpringBootConfiguration {}
//里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用
@Component
public @interface Configuration {}
@AutoConfigurationPackage :自动配置包
//AutoConfigurationPackage的子注解
//Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
@EnableAutoConfiguration开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration 告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;
AutoConfigurationImportSelector :自动配置导入选择器,给容器中导入一些组件
总结原理:
@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class) 来加载配置类。
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean
并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean
四.自定义启动器
需求: 自定义redis-starter,要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean
参考: 可以参考mybatis启动类的应用
实现步骤:
创建redis-spring-boot-autoconfigure模块
创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure的模块
在redis-spring-boot-autoconfigure模块中初始化Jedis的Bean,并定义METAINF/spring.factories文件
在测试模块中引入自定义的redis-starter依赖,测试获取Jedis的Bean,操作redis。