Spring如何根据条件创建bean,@Conditional注解使用方式
作者:NameExist
Spring根据条件创建bean,@Conditional注解使用
spring提供了一个基于条件创建bean的注解@Conditional。
@Conditional注解定义
//1.可作用于类(接口)、方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
//2.需要传入Condition类型的参数(继承Condition接口)
Class<? extends Condition>[] value();
}@Conditional注解使用
1、定义一个类实现Condition接口,返回true则会创建bean,false则不会创建
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return true;
}
}2,定义java配置类
@Configuration
public class BeanConfig {
@Bean
@Conditional({MyCondition.class})
public User user(){
User user = new User();
user.setName("小明");
return user;
}
}3、获取bean
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
//获取bean,如果MyCondition返回flase则会报NoSuchBeanDefinitionException,注解注入@Autowired(required = false)
User u = (User) context.getBean("user");
System.out.println(u.toString());
}@Conditional注解常用子注解
springboot封装conditional注解,在包org.springframework.boot.autoconfigure.condition下。
1、ConditionalOnProperty :当配置文件熟悉值与注解配置的值一致时才会创建bean。prefix配置文件的前缀,name字段名,havingValue字段值
//当配置文件 user.enable=1时才会创建bean,否则不会创建
@ConditionalOnProperty(prefix = "user", name = "enable",havingValue = "1")
@Bean
public User user(){
User user = new User();
user.setName("name");
return user;
}2、ConditionalOnBean 和 ConditionalOnMissingBean:当某一个bean存在或不存在时才会创建bean。
@Bean
public Dept dept(){
return new Dept();
}
/**
* ConditionalOnBean 和 ConditionalOnMissingBean 跟bean的创建顺序有关,
* 如果先创建了则ConditionalOnBean修饰的bean被创建,后创建传入的bean类型则ConditionalOnMissingBean修饰的bean被创建
* @return
*/
@ConditionalOnBean(Dept.class)
@Bean
public User user(){
User user = new User();
user.setName("name");
return user;
}
@ConditionalOnMissingBean(Dept.class)
@Bean
public User user1(){
User user = new User();
user.setName("name-1");
return user;
}3、ConditionalOnExpression:多个条件判断,满足则创建bean,可以用and 和 or。
/**
* 多个条件判断
* @return
*/
//@ConditionalOnExpression("'${user.enable}' == '1' and '${user.open}' == '2'")
@ConditionalOnExpression("'${user.enable}' == '1' or '${user.open}' == '2'")
@Bean
public User user(){
User user = new User();
user.setName("name");
return user;
}按照条件向Spring容器中注册bean
1.@Conditional注解概述
@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}- @Conditional注解不仅可以添加到类上,也可以添加到方法上。
- @Conditional注解中,还存在着一个Condition类型或者其子类型的Class对象数组。
package org.springframework.context.annotation;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}是一个接口,所以在使用@Conditional注解时,需要写一个类来实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后就可以使用在@Conditional注解中定义的类来检查了。

2.向Spring容器注册bean
2.1.不带条件注册bean
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
//通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
@Scope("prototype")
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("liqb", 24);
}
/**
* 创建person实例 名为bill
* @author liqb
* @date 2023-04-23 12:47
* @return
*/
@Bean("bill")
public Person person01() {
return new Person("Bill Gates", 62);
}
/**
* 创建person实例 名为linus
* @author liqb
* @date 2023-04-23 12:47
* @return
*/
@Bean("linus")
public Person person02() {
return new Person("linus", 48);
}
}测试两个bean是否被注册到Spring容器中
/**
* 测试bill,linus是否被注入到spring中
* @author liqb
* @date 2023-04-23 12:50
*/
@Test
public void test06() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig02.class);
// 查看IOC容器中Person这种类型的bean都有哪些
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
for (String name : namesForType) {
System.out.println(name);
}
}输出的结果信息如下所示:

结论:说明默认情况下,Spring容器会将单实例并且非懒加载的bean注册到IOC容器中。
2.2.带条件注册bean
需求:比如,如果当前操作系统是Windows操作系统,那么就向Spring容器中注册名称为bill的Person对象;如果当前操作系统是Linux操作系
统,那么就向Spring容器中注册名称为linus的Person对象。要想实现这个需求,我们就得要使用@Conditional注解了。
- LinuxCondition
package com.tianxia.springannotation.config.configCondition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 判断操作系统是否是Linux系统
* @author liqb
* @date 2023-04-23 12:56
**/
public class LinuxCondition implements Condition{
/**
* 匹配方法
* @author liqb
* @date 2023-04-23 12:57
* @param context 判断条件能使用的上下文(环境)
* @param metadata 当前标注了@Conditional注解的注释信息
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断操作系统是否是Linux系统
// 1. 获取到bean的创建工厂(能获取到IOC容器使用到的BeanFactory,它就是创建对象以及进行装配的工厂)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 获取到类加载器
ClassLoader classLoader = context.getClassLoader();
// 3. 获取当前环境信息,它里面就封装了我们这个当前运行时的一些信息,包括环境变量,以及包括虚拟机的一些变量
Environment environment = context.getEnvironment();
// 4. 获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// 在这儿还可以做更多的判断,比如说我判断一下Spring容器中是不是包含有某一个bean,就像下面这样,如果Spring容器中果真包含有名称为person的bean,那么就做些什么事情...
boolean definition = registry.containsBeanDefinition("person");
String property = environment.getProperty("os.name");
if (property.contains("linux")) {
return true;
}
return false;
}
}理解:通过context的getRegistry()方法获取到的bean定义的注册对象,即BeanDefinitionRegistry对象。
如下所示,可以看到它是一个接口
package org.springframework.beans.factory.support;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.AliasRegistry;
// spring容器中所有的bean都是通过BeanDefinitionRegistry对象来进行注册的
// 因此我们可以通过它来查看Spring容器中注册了哪些bean
public interface BeanDefinitionRegistry extends AliasRegistry {
// 该方法表明我们可以通过BeanDefinitionRegistry对象向5pring容器中注册一个bean
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
// 该方法表明我们可以通过BeanDefinitionRegistry对象在Spring容器中移除一个bean
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 该方法表明我们可以通过BeanDefinitionRegistry对象查看某个bean的定义信息
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 该方法表明我们可以通过BeanDefinitionRegistry对象查看对象Spring
// 容器中是否包含某一个bean的定义
boolean containsBeanDefinition(String beanName);
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String beanName);
}可以通过BeanDefinitionRegistry对象向Spring容器中注册一个bean、移除一个bean、查询某一个bean的定义信息或者判断Spring容器
中是否包含有某一个bean的定义。
- WindowsCondition
package com.tianxia.springannotation.config.configCondition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 判断操作系统是否是Linux系统
* @author liqb
* @date 2023-04-23 14:40
**/
public class WindowsCondition implements Condition {
/**
* 匹配方法
* @author liqb
* @date 2023-04-23 12:57
* @param context 判断条件能使用的上下文(环境)
* @param metadata 当前标注了@Conditional注解的注释信息
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Windows")) {
return true;
}
return false;
}
}配置类中使用@Conditional注解添加条件,添加该注解后的方法如下所示。
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.config.configCondition.LinuxCondition;
import com.tianxia.springannotation.config.configCondition.WindowsCondition;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
//通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
@Scope("prototype")
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("liqb", 24);
}
/**
* 创建person实例 名为bill
* @author liqb
* @date 2023-04-23 12:47
* @return
*/
@Bean("bill")
@Conditional({WindowsCondition.class})
public Person person01() {
return new Person("Bill Gates", 62);
}
/**
* 创建person实例 名为linus
* @author liqb
* @date 2023-04-23 12:47
* @return
*/
@Bean("linus")
@Conditional({LinuxCondition.class})
public Person person02() {
return new Person("linus", 48);
}
}在运行测试方法,输出的结果信息如下所示:

输出结果中不再含有名称为linus的bean了,这说明程序中检测到当前操作系统为Windows 10之后,没有向Spring容器中注册名称为linus的bean。
@Conditional注解也可以标注在类上,标注在类上的含义是:只有满足了当前条件,这个配置类中配置的所有bean注册才能生效,也就是对配置类中的组件进行统一设置。
package com.tianxia.springannotation.config;
import com.tianxia.springannotation.config.configCondition.LinuxCondition;
import com.tianxia.springannotation.config.configCondition.WindowsCondition;
import com.tianxia.springannotation.entity.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* 配置类
* @author liqb
* @date 2023-04-23 9:45
**/
@Configuration
// 满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({LinuxCondition.class})
public class MainConfig02 {
/**
* 创建person实例
* @author liqb
* @date 2023-04-23 09:46
* @return
*/
@Bean("person02")
//通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
@Scope("prototype")
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("liqb", 24);
}
/**
* 创建person实例 名为bill
* @author liqb
* @date 2023-04-23 12:47
* @return
*/
@Bean("bill")
@Conditional({WindowsCondition.class})
public Person person01() {
return new Person("Bill Gates", 62);
}
/**
* 创建person实例 名为linus
* @author liqb
* @date 2023-04-23 12:47
* @return
*/
@Bean("linus")
@Conditional({LinuxCondition.class})
public Person person02() {
return new Person("linus", 48);
}
}运行测试方法之后,发现输出的结果信息如下所示:

没有任何bean的定义信息输出,这是因为程序检测到了当前操作系统为window,没有向Spring容器中注册任何bean的缘故导致的。
3.@Conditional的扩展注解
| @Conditional扩展注解 | 作用 (判断是否满足当前指定条件) |
|---|---|
| @ConditionOnJava | 系统的Java版本是否符合要求 |
| @ConditionOnBean | 容器中存在指定Bean |
| @ConditionOnMissingBean | 容器中不存在指定Bean |
| @ConditionOnExpression | 满足SpEL表达式指定 |
| @ConditionOnClass | 系统中有指定的类 |
| @ConditionOnMissingClass | 系统中没有指定的类 |
| @ConditionOnSingleCandidate | 容器中只有一个指定的bean,或者这个bean是首选bean |
| @ConditionOnProperty | 系统中指定的属性是否有指定的值 |
| @ConditionOnResource | 类路径下是否存在指定资源文件 |
| @ConditionalOnWebApplication | 当前是web环境 |
| @ConditionalOnNoWebApplication | 当前不是web环境 |
| @ConditionalOnJndi | JNDI存在指定项 |
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
