夯实spring(二十):@Conditional注解详解
1,@Conditional注解
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@FunctionalInterface
public interface Condition {
//context:条件上下文,ConditionContext接口类型的,可以用来获取容器中的各种信息
//metadata:用来获取被@Conditional标注的对象上的所有注解信息
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@Conditional注解是从spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。
这个注解只有一个value参数,Condition类型的数组,Condition是一个接口,表示一个条件判断,内部有个方法matches返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立,所以是逻辑与的关系。
比如可以通过@Conditional来控制bean是否需要注册,控制被@Configuration标注的配置类是需要需要被解析等。
上面的接口Condition 的方法matches的参数ConditionContext
public interface ConditionContext {
/**
* 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
*/
BeanDefinitionRegistry getRegistry();
/**
* 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
/**
* 返回当前spring容器的环境配置信息对象
*/
Environment getEnvironment();
/**
* 返回资源加载器
*/
ResourceLoader getResourceLoader();
/**
* 返回类加载器
*/
@Nullable
ClassLoader getClassLoader();
}
spring的配置类
- 类上有@Compontent注解
- 类上有@Configuration注解
- 类上有@CompontentScan注解
- 类上有@Import注解
- 类上有@ImportResource注解
- 类中有@Bean标注的方法
类中有上面任意注解之一的就属于配置类
Spring对配置类的处理主要分为2个阶段
配置类解析阶段
会得到一批配置类的信息,和一些需要注册的bean
bean注册阶段
将配置类解析阶段得到的配置类和需要注册的bean注册到spring容器中
spring中处理这2个过程会循环进行,直到完成所有配置类的解析及所有bean的注册:
- 通常通过new AnnotationConfigApplicationContext()传入多个配置类来启动spring容器
- spring对传入的多个配置类进行解析
- 配置类解析阶段:这个过程就是处理配置类上面6中注解的过程,此过程中又会发现很多新的配置类,比如@Import导入的一批新的类刚好也符合配置类,而被@CompontentScan扫描到的一些类刚好也是配置类;此时会对这些新产生的配置类进行同样的过程解析
- bean注册阶段:配置类解析后,会得到一批配置类和一批需要注册的bean,此时spring容器会将这批配置类作为bean注册到spring容器,同样也会将这批需要注册的bean注册到spring容器
- 经过上面第3个阶段之后,spring容器中会注册很多新的bean,这些新的bean中可能又有很多新的配置类
- Spring从容器中将所有bean拿出来,遍历一下,会过滤得到一批未处理的新的配置类,继续交给第3步进行处理
- 第三步到第六步,这个过程会经历很多次,直到完成所有配置类的解析和bean的注册
如果将Condition接口的实现类作为配置类上@Conditional中,那么这个条件会对两个阶段(配置类解析阶段、bean注册阶段)都有效,如果想控制某个阶段,比如可以让他解析,但是不能让他注册,此时就就需要用到另外一个接口了:ConfigurationCondition
对于Condition接口我们可以:
- 在配置类上面加上@Conditional注解,来控制是否需要解析这个配置类,配置类如果不被解析,那么这个配置上面6种注解的解析都会被跳过。
- 在被注册的bean上面加上@Conditional注解,来控制这个bean是否需要注册到spring容器中
2,@Conditional的使用
- 自定义一个类,实现Condition或ConfigurationCondition接口,实现matches方法
- 在目标对象上使用@Conditional注解,并指定value的指为自定义的Condition类型
来个案例:
自定义一个Condition类实现Condition接口:
public class Condition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
配置类Config1,在配置类上面使用上面Condition1 这个条件
@Configuration
@Conditional(Condition1.class)
public class Config1{
@Bean
public String hello() {
return "Hello,World";
}
}
测试输出:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);
String hello = (String) context.getBean("hello");
System.out.println(hello);
}
}
此时会输出报错:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'hello' available
我们把Config1的@Conditional(Condition1.class)去掉再运行输出:
Hello,World
上面的案列是阻止配置类的,再来一个阻止bean注册的:
public class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
}
@Configuration
public class Config2 {
@Bean
@Conditional(Condition1.class)
public Car car1() {
return new Car("五菱",10000);
}
@Bean
public Car car2() {
return new Car("五菱MINI",20000);
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
上面注册两个bean,car1和car2,car1被@Conditional(Condition1.class)标注
测试输出:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config2.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(context.getBean(name));
}
}
}
org.springframework.context.annotation.ConfigurationClassPostProcessor@4c6e276e
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@534df152
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@52e677af
org.springframework.context.event.EventListenerMethodProcessor@35083305
org.springframework.context.event.DefaultEventListenerFactory@8e0379d
com.chen.Config2$$EnhancerBySpringCGLIB$$ad8038a0@341b80b2
Car{
name='五菱MINI', price=20000.0}
可以看到只有car2注册了。如果把car1的@Conditional(Condition1.class)去掉,car1就会注册进来。
3,Condition指定优先级
@Condtional中value指定多个Condtion的时候,默认情况下会按顺序执行。我们也可以指定Condition的顺序。自定义的Condition可以实现PriorityOrdered接口或者继承Ordered接口,或者使用@Order注解,通过这些来指定这些Condition的优先级。
排序规则是先按PriorityOrdered排序,然后按照order的值进行排序;也就是:PriorityOrdered
升序,order值升序
我们通过使用@Order注解来指定一下多个Condtion的顺序,其他的方法可以自己测试。
来两个Condition的实现类:
@Order(1)
public class Condition1 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println("我是Condition1");
return true;
}
}
@Order(2)
public class Condition2 implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println("我是Condition2");
return true;
}
}
来个配置类,使用上面的实现类:
@Configuration
@Conditional(value = {
Condition2.class,Condition1.class})
public class Config3 {
}
注意@Conditional(value =
{Condition2.class,Condition1.class})是Condition2.class,Condition1.class顺序写的
测试输出:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config3.class);
}
}
我是Condition1
我是Condition2
我是Condition1
我是Condition2
我是Condition1
我是Condition2
可以看到是实现输出我是 Condition1,然后才是 我是Condition2。是按照@Order的,不是按照编写顺序的。
4,接口ConfigurationCondition
public interface ConfigurationCondition extends Condition {
/**
* 条件判断的阶段,是在解析配置类的时候过滤还是在创建bean的时候过滤
*/
ConfigurationPhase getConfigurationPhase();
/**
* 表示阶段的枚举:2个值
*/
enum ConfigurationPhase {
/**
* 配置类解析阶段,如果条件为false,配置类将不会被解析
*/
PARSE_CONFIGURATION,
/**
* bean注册阶段,如果为false,bean将不会被注册
*/
REGISTER_BEAN
}
}
接口ConfigurationCondition继承了Condition 接口,并多了一个方法getConfigurationPhase()
前面说过,配置类的处理会依次经过2个阶段:配置类解析阶段和bean注册阶段,Condition接口类型的条件会对这两个阶段都有效
而ConfigurationCondition接口可以控制在哪个阶段起作用。
如:
public class Condition3 implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN; //指定条件在bean注册阶段,这个条件才有效
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
总结:
- @Conditional注解可以标注在spring需要处理的对象上(配置类、@Bean方法),相当于加了个条件判断,通过判断的结果,是否要继续处理被这个注解标注的对象,多个条件是逻辑与的关系
- spring处理配置类大致有2个过程:解析配置类、注册bean,这两个过程中都可以使用@Conditional来进行控制spring是否需要处理这个过程,Condition默认会对2个过程都有效
- ConfigurationCondition控制得更细一些,可以控制到具体那个阶段使用条件判断
还没有评论,来说两句吧...