好久不见,最近发现公司有使用到 spring
提供条件装配注解,所以有了这篇文章。
首先准备3个类。
Student
:
@Component
public class Student {
public Student() {
System.out.println("init student");
}
}
Teacher
:
@Component
public class Teacher {
public Teacher() {
System.out.println("init Teacher");
}
}
启动类:
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("=========================================");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.yuandragon.startalk.test.spring.condition");
System.out.println("=========================================");
}
}
启动 main
方法
=========================================
init Teacher
init student
=========================================
打印的内容是意料之中的,但是现在有了一些新的需求。
假设 student
需要强依赖于 teacher
,如果没有老师,那么也就没有学生,这时我们需要一个机制在 student
类加载时判断,如果 teacher
类不存在,那么 student
类就不加载。
spring
猜到我们会有这种需求,所以给我们提供了一个注解 @Conditional
,正如名字的意思 '有条件的' 加载 bean
,使用该注解时需要小心,否则会出现奇怪的现象。
@Conditional
注解内部需要我们传入一个 Condition
集合
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
matches
方法的返回值决定着使用 Conditional
注解的类是否会被创建
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
实现 Condition
类
@Order
public class TeacherCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 从beanFactory中获取bean的名称
String[] beanNamesForType = beanFactory.getBeanNamesForType(Teacher.class);
boolean b = beanNamesForType != null && beanNamesForType.length > 0;
return b;
}
}
如果可以从 beanFactory
中获取到 beanName
则说明该类已经交给 spring
管理了,可以证明 teacher
存在。
在 student
类上加上 @Conditional
注解
@Component
@Conditional(value = {TeacherCondition.class})
public class Student {
执行 main
方法
=========================================
init Teacher
=========================================
你会惊讶的发现控制台打印了teacher
,但是没有打印 student
,明明 teacher
都被加载了,这是怎么回事?这里面涉及到一个 bean
加载的顺序的问题,我们修改一下代码。
新增 ConditionConfiguration
类,并把 Teacher
和 Student
类上的 @Component
注解去除
@Configuration
public class ConditionConfiguration {
@Bean
Teacher teacher(){
return new Teacher();
}
@Bean
@Conditional(value = {TeacherCondition.class})
Student student(){
return new Student();
}
}
通过这种方式,指定 bean
的加载顺序,再次执行 main
方法,2个类都完成加载了
=========================================
init Teacher
init student
=========================================
@Conditional
是 spring
提供的注解,而 springBoot
则提供了一些更加易用的注解,如 @ConditionalOnBean
,@ConditionalOnBean
注解使用起来比较方便,不用自己实现 condition , 修改 Student 类上的注解
@Bean
@ConditionalOnBean(Teacher.class)
Student student(){
return new Student();
}
执行 main
方法,结果也是符合预期的
=========================================
init Teacher
init student
=========================================
@ConditionalOnBean
是如何实现的呢?
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/**
* The class types of beans that should be checked. The condition matches when beans
* of all classes specified are contained in the {@link BeanFactory}.
* @return the class types of beans to check
*/
Class<?>[] value() default {};
...
}
@ConditionalOnBean
注解内部还是使用的 @Conditional
注解,OnBeanCondition
和我们之前实现的TeacherCondition
类效果是一样的,都是判断 bean
是否存在,但是 OnBeanCondition
内部的判断比较复杂。
@ConditionalOnBean
注解也会因为 bean 的加载顺序而出现异常,我们将 teacher
和 student
的顺序调整一下。
public class ConditionConfiguration {
@Bean
@ConditionalOnBean(Teacher.class)
Student student(){
return new Student();
}
@Bean
Teacher teacher(){
return new Teacher();
}
}
异常出现了
=========================================
init Teacher
=========================================
无论是使用 @Conditional
还是使用 @ConditionalOnBean
都会因为 bean 的加载顺序而出现加载异常的问题,所以并不推荐大家日常使用,如果一定要使用这两个注解的话,那么你需要保证 bean
加载的顺序。
springBoot 还提供了很多条件装配的注解,而 springBoot 提供的注解基本都是在注解上再使用 @Conditional 注解,感兴趣可以看看。




