Spring中@Conditional注解 讲解与使用

2,221 阅读3分钟

@Conditional介绍

作用

@Conditional是Spring4新提供的注解,能够根据一定的条件进行判断,满足条件就给容器注入bean。

@Conditional定义

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	Class<? extends Condition>[] value();
}

从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:

public interface Condition {
	boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2)
}

Condition是一个接口,返回true就注入bean,false则不注入。

@Conditional 使用示例

public class User {
	
    private String name;
    private String age;
    
    public User(String name, String age) {
    	this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
    	return "User{" + "name=" + name + ", age=" + age + "}";
    }
    
    // getter/setter方法省略
}

编写配置类BeanConfig,注入两个User实例

@Configuration
public class BeanConfig {
	
    @Bean(name = "bill")
    public User user1() {
    	return new User("Bill Gates", 62);
    }
    
    @Bean(name = "linus")
    public User user2() {
    	return new User("Linus", 48);
    }
}

编写测试类,查看User实例是否注入

public class ConditionalTest {
	
   AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
   
   @Test
   public void test1() {
   		Map<String, User> map = applicationContext.getBeansByType(User.class);
        System.out.println(map);
   }
   
}

输出结果如下:

{bill=User{name=Bill Gates, age=62}, linus=User{name=Linus, age=48}}

这时候问题来了,如果我们想根据当前操作系统来注入User实例(windows系统下注入bill, linux系统下注入linus),那么该怎么做呢?

这就需要我们用到@Conditional注解了。

首先我们先继承Condition接口,自定义判断条件

public class WindowsCondition implements Condition {
	
    /**
    * @param conditionContext:判断条件能使用的上下文环境
    * @param annotatedTypeMetadata:注解所在位置的注解信息
    */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    	//获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        //获取bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        
        //获取当前系统名
       	String property = environment.getProperty("os.name");
        //如果包含 Windows则说明是windows系统,返回true,否则返回false
        if(property.contains("Windows")) {
        	return true;
        }
        return false;
    }
}

值得一提的是 conditionContext方法提供了多种方法,方便获取各种信息,也是SpringBoot中@ConditionalOnXXX注解多样扩展的基础。

public class LinuxCondition implements Condition {
	
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    Environment environment = conditionContext.getEnvironment();
    
    String property = environment.getProperty("os.name");
    if(property.contains("Linux")) {
    	return true;
    }
    return false;
    
    }
}

将上面配置的Condition子类传递给@Conditonal注解

@Configuration
public class BeanConfig {
	
    //只有一个类时,大括号可以省略
    @Conditional({WindowsCondition.class})
    @Bean(name = "bill")
    public User user1() {
    	return new User("Bill Gates", 62);
    }
    
    @Conditional({LinuxCondition.class})
    @Bean(name = "linus")
    public User user2() {
    	return new User("Linus", 48);
    }
}

测试

public class ConditionalTest {
    
   AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
   
   @Test
   public void test1() {
       String osname = applicationContext.getEnvironment().getProperty("os.name");
       System.out.println("当前系统为" + osname);
       Map<String, User> map = applicationContext.getBeansOfType(User.class);
       System.out.println(map);
   }
   
}

运行结果:

当前系统为Windows 10
{bill=User{name='Bill Gates', age=62}}

@Conditional标注在类上

我们从@Conditional注解的定义可以看出,@Conditional注解不仅可以标注在方法上,也是可以标注在类上的。接下来就来看看标注在类上的效果:

@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
	
    @Bean(name = "bill")
    public User user1() {
    	return new User("Bill Gates", 62);
    }
    
    @Bean(name = "linus")
    public User user2() {
    	return new User("Linus", 48);
    }
}

我们可以猜到,上面代码的效果就是将两个Bean都注入到了IoC容器。

传入多个Condition条件类

上面讲到,@Conditional注解传入的是一个数组。那么,当我们传入多个条件类的时候,会产生什么效果呢?

/**
* 始终返回false
*/
public class NegativeCondition implements Condition {
	
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    	return false;
    }
}
@Conditional({WindowsCondition.class, NegativeCondition.class})
@Configuration
public class BeanConfig {
	
    @Bean(name = "bill")
    public User user1() {
    	return new User("Bill Gates", 62);
    }
    
    @Bean(name = "linus")
    public User user2() {
    	return new User("Linus", 48);
    }
}

此时结果为两个bean都没有被注入。

原因是: WindowsCondition判断条件为true,但是NegativeCondition判断条件为false。即当存在多个Condition类时,需要同时满足才会注入bean。