一、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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Beanpublic 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: localhostport: 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 {@Beanpublic Jedis jedis(RedisProperties redisProperties){return new Jedis(redisProperties.getHost(),redisProperties.getPort());}
}
2.3 在测试类中获取,方法与上面相同
得到结果:BinaryJedis{Connection{DefaultJedisSocketFactory{localhost:6060}}}