一、Condition条件判断功能
Condition 是在Spring 4.0 增加的条件判断功能,其主要作用是判断条件是否满足,从而决定是否初始化并向容器注入Bean对象。通过@Conditional注解及其一系列的其他相关注解实现。
在Spring Boot中,条件匹配(Condition Evaluation)是一个非常重要的特性,它允许开发者根据特定的条件来决定是否创建和配置Bean。这一机制在Spring框架中是通过@Conditional注解及其一系列的变体来实现的。
以下是一些关于Spring Boot中条件匹配的基础知识:
1.@Conditional 注解
@Conditional 注解可以放在类或者方法上。当你放置在配置类上时,只有当所有指定的条件都满足时,配置类中的Bean才会被创建。放置在Bean声明的方法上时,只有当条件满足时,对应的Bean才会被注册。
@Configuration
@Conditional(YourCondition.class)
public class YourConfig {
// ...
}
2. 条件类
matches 方法两个参数:
context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
metadata:元数据对象,用于获取注解属性
你需要创建一个实现了Condition接口的类,来定义你的条件。
public class YourCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 你的条件逻辑
return true; // 或者 false
}
}
3. 内置条件注解
Spring Boot提供了一系列内置的条件注解,可直接使用:
- @ConditionalOnBean: 当指定的Bean存在时。
- @ConditionalOnMissingBean: 当指定的Bean不存在时。
- @ConditionalOnClass: 当指定的字节码文件存在时。
- @ConditionalOnMissingClass: 当指定的字节码文件不存在时。
- @ConditionalOnProperty: 当指定的属性有指定的值时。
- @ConditionalOnResource: 当指定的资源存在时。
- @ConditionalOnWebApplication: 当项目是一个Web应用程序时。
- @ConditionalOnNotWebApplication: 当项目不是一个Web应用程序时。
4. 示例1
假设我们只有在类SomeService存在时才想要创建一个Bean。
@Configuration
public class ExampleConfig {
@Bean
@ConditionalOnClass(SomeService.class)
public SomeBean someBean() {
return new SomeBean();
}
}
在这个例子中,SomeBean对象只会在SomeService类存在时被创建。
5.示例2
在Spring IOC容器中有一个User的Bean对象:
1.导入Jedis坐标后,加载该User;没导入,则不加载,通过注解实现。
- 创建一个User对象
public class User {
}
- 在配置类UserConfig中加载User对象
@Configuration
public class UserConfig {
@Bean
@Conditional(value = ClassCondition.class)
public User user(){
return new User();
}
}
- 在ClassCondition类中判断Jedis坐标是否导入
public class ClassCondition implements Condition {
@Override
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;
}
}
2.动态指定文件,通过自定义注解实现
- 自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(value = ClassCondition.class)
public @interface ConditiononClass {
String[] value();
}
- 配置类UserConfig,使用自定义注解@ConditiononClass实现
动态指定坐标,将坐标保存在String类型的数组中,只有数组中所有的坐标都被导入时,才会加载对象
@ConditiononClass("redis.clients.jedis.Jedis")
public User user1(){
return new User();
}
@ConditiononClass(value = {"redis.clients.jedis.Jedis","com.alibaba.fastjson.JSON"})
public User user1(){
return new User();
}
- 在ClassCondition类中判断String类型的数组中的坐标是否导入
public class ClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//动态坐标或坐标数组
Map<String, Object> map = metadata.getAnnotationAttributes(ConditiononClass.class.getName());
System.out.println(map);
String[] classname = (String[])map.get("value");
boolean flag=true;
try {
for (String name:classname){
Class<?> aClass = Class.forName(name);
}
} catch (ClassNotFoundException e) {
flag=false;
}
return flag;
}
}
3.启动类
@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);
}
}
二、@Enable注解
SpringBoot中提供了很多Enable开头的注解,都用于启动某些功能。其底层原理是使用@Import注解导入一些配置类,实现Bean对象的加载。使用@Import导入的类会被Spring加载到IOC容器中。
源代码分析:
@Import提供4中用法:
1. 导入Bean
- 创建enable模块
创建一个User的Bean对象
创建一个配置类,加载User对象
- 测试模块
依赖enable的模块
<dependency>
<groupId>com.apesource</groupId>
<artifactId>enable</artifactId>
<version>0.0.1SNAPSHOT</version>
</dependency>
在启动类测试获取User
@SpringBootApplication
@Import(User.class)
public class SpringbootEnable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnable01Application.class, args);
User user = run.getBean(User.class);
System.out.println(user);
}
}
方法二:在enable模块写一个@Enable注解
在测试模块的启动类上方通过@EnableUser注解导入Bean对象
2. 导入配置类
在启动类上方导入enable模块的config配置类
也可以在启动类上扫描enable模块的config配置类
3. 导入 ImportSelector 实现类
- enable模块:
创建一个Student 的对象
MyImportSelector类,实现了ImportSelector接口,重写接口中的selectImports方法,往String[]中传入两个Bean对象
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.springboot_enable_02_other.domain.User","com.example.springboot_enable_02_other.domain.Student"};
}
}
- 测试模块
@SpringBootApplication
//ImportSelector实现类
@Import(MyImportSelector.class)
public class SpringbootEnable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnable01Application.class, args);
//ImportSelector实现类
User user = run.getBean(User.class);
System.out.println(user);
Student student = run.getBean(Student.class);
System.out.println(student);
}
}
4. 导入 ImportBeanDefinitionRegistrar 实现类
- enable模块
MyImportBeanDefinitionRegister类,实现了ImportBeanDefinitionRegistrar接口,重写接口中的registerBeanDefinitions方法,往String[]中传入两个Bean对象
public class MyImportBeanDefinitionRegister 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);
}
}
- 测试模块
@SpringBootApplication
//ImportBeanDefinitionRegister实现类
@Import(MyImportBeanDefinitionRegister.class)
public class SpringbootEnable01Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnable01Application.class, args);
//ImportBeanDefinitionRegister实现类
User user = run.getBean(User.class);
System.out.println(user);
}
}
三、@EnableAutoConfiguration 注解
@SpringBootApplication
标明这个类是一个主启动类
@SpringBootApplication注解内部详细代码如下
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
@ComponentScan
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@EnableAutoConfiguration:开启自动配置功能
当SpringBoot扫描到@EnableAutoConfiguration注解时,会将所有包名为autoconfigure的包下的META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value里的所有xxxConfiguration类加载到IOC容器中。
而xxxAutoConfiguration类一般都会有@ConditionalOnxxx注解,通过这些条件注解来判断是否真正的创建xxxConfiguration对象。
@AutoConfigurationPackage :自动配置包
Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器
spring boot处理@EnableAutoConfiguration源码分析
从EnableAutoConfiguration进入AutoConfigurationImportSelector
AutoConfigurationImportSelector实现了DeferredImportSelector接口,而DeferredImportSelector这个接口实现了ImportSelector接口
所以在AutoConfigurationImportSelector类重写了selectImports方法
进入getAutoConfigurationEntry方法
进入getCandidateConfigurations方法
getCandidateConfigurations会到classpath下的读取META-INF/spring.factories文件的配置,并返回一个字符串数组。
当SpringBoot应用启动时,会自动加载这些配置类
在所有包名叫做autoConfiguration的包下面都有META-INF/spring.factories文件
总结原理:
- @EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class) 来加载配置类。
- 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot应用启动时,会自动加载这些配置类,初始化Bean。但并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean
四、自定义启动器
自定义redisstarter,要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean
直接设置port、host
1.1 创建redisspringbootautoconfigure模块
- 导入Jedis坐标
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
- 初始化Jedis的Bean
@Configuration
public class RedisAutoconfiguration {
@Bean
public Jedis jedis(){
return new Jedis("localhost",6379);
}
}
- 在resources中定义METAINF/spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.apesource.redisspringbootautoconfigure.RedisAutoconfiguration
1.2 创建redisspringbootstarter模块
依赖redisspringbootautoconfigure的模块
<dependency>
<groupId>com.apesource</groupId>
<artifactId>redisspringbootautoconfigure</artifactId>
<version>0.0.1SNAPSHOT</version>
</dependency>
1.3 测试模块
- 引入自定义的redisstarter依赖
<dependency>
<groupId>com.apesource</groupId>
<artifactId>redisspringbootstarter</artifactId>
<version>0.0.1SNAPSHOT</version>
</dependency>
- 测试获取Jedis的Bean,操作redis。
@SpringBootApplication
public class SpringbootStarter01Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootStarter01Application.class, args);
Jedis bean = run.getBean(Jedis.class);
System.out.println(bean);
}
}
//BinaryJedis{Connection{DefaultJedisSocketFactory{localhost:6379}}}
在application.yml文件设置port、host
2.1 测试类
spring:
redis:
host: localhost
port: 6060
2.2 redisspringbootautoconfigure模块
- 批量注入写在yml文件中的host和port
//批量注入
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
//如果有值就存入,没有值就使用默认的
private String host="localhost";
private int port=6379;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
- 初始化Jedis的Bean时传入redisProperties对象(即:port、host),通过get方法获得对应的值
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoconfiguration {
@Bean
public Jedis jedis(RedisProperties redisProperties){
return new Jedis(redisProperties.getHost(),redisProperties.getPort());
}
}
2.3 在测试类中获取,方法与上面相同
得到结果:BinaryJedis{Connection{DefaultJedisSocketFactory{localhost:6060}}}