暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring 中条件装配注解

dragon元 2021-06-24
674

好久不见,最近发现公司有使用到 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 注解,感兴趣可以看看。


文章转载自dragon元,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论