@Conditional условной сборки Spring Framework

Spring
@Conditional условной сборки Spring Framework

@Conditional условной сборки Spring Framework

предисловие

Те, кто знаком со SpringBoot, знакомы с условными аннотациями.В проекте SpringBoot условные аннотации широко используются, и многие аннотации, производные от условий, расширены. Хотя Conditional был значительно расширен в SpringBoot, он был предложен в Spring Framework 4.0, поэтому эта статья по-прежнему основана на Spring Framework.

Рекомендуемое чтение черные глаза の личный блог Регистрация компонента FactoryBean of Spring Framework @Import регистрации компонентов Spring Framework @Component регистрации компонентов Spring Framework

@Условное описание

нужно использовать@ConditionalОбратите внимание, вы должны сначала понятьConditionaинтерфейс, связанный с@ConditionalИспользуя аннотации вместе, мы также можем видеть из исходного кода, что они используют@ConditionalВ аннотации должна быть указана реализацияConditionaКласс интерфейса.

@Target({ElementType.TYPE, ElementType.METHOD})
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();
}

существуетConditionaВ интерфейсе определен только один методmatches, когда Spring регистрирует компонент, он также основывается на возвращаемом значении этого методаTRUE/FALSEрешить, регистрировать ли компонент в контейнере spring

@FunctionalInterface
public interface Condition {
	/**
	 * Determine if the condition matches.
	 * @param context 条件判断的上下文环境
	 * @param metadata 正在检查的类或方法的注解元数据
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

существуетmatchesв мы можем получитьConditionContextИнтерфейс, по этому объекту интерфейса можно получитьBeanDefinitionRegistry,ConfigurableListableBeanFactoryи другая важная информация об объекте, в соответствии с этими объектами, вся информация, содержащаяся в инициализации контейнера Spring, может быть получена и проверена, и в сочетании с бизнес-требованиями может быть реализовано пользовательское суждение о состоянии во время регистрации компонента.

@Условное использование

Сначала определите два обычных класса JavaBean

@Data
public class Test {
    private String id = "@Bean";
}

@Data
public class Test2 {
    private String id = "@Conditional";
}

настроив класс и@BeanАннотация для регистрации компонентов в контейнере Spring

/**
 * spring组件配置类
 */
@Configuration
public class TestConfiguration {
    /**
     * 向spring容器中注册Test 类型下beanName为test的组件
     */
    @Bean
    public Test test() {
        return new Test();
    }

    /**
     * 根据TestCondition接口的条件判断向spring容器中注册Test2组件
     */
    @Bean
    @Conditional(TestCondition.class)
    public Test2 test2() {
        return new Test2();
    }
}

пользовательская реализацияConditionинтерфейс

public class TestCondition implements Condition {
    /**
     * 当IOC容器中包含 Test类的bean定义信息时,条件成立
     *
     * @param context  条件判断的上下文环境
     * @param metadata 正在检查的类或方法的元数据
     * @return 条件是否成立
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
        return ArrayUtils.isNotEmpty(testBeanNames);
    }
}

Добавить класс начальной загрузки контейнера Spring

/**
 * spring 容器启动引导类,测试 @Conditional 功能
 */
@ComponentScan("com.spring.study.ioc.condition")
public class TestConditionalBootstrap {
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestConditionalBootstrap.class);
        String[] names = applicationContext.getBeanNamesForType(Test.class);
        System.out.println("---Test bean names : " + Arrays.asList(names));
        names = applicationContext.getBeanNamesForType(Test2.class);
        System.out.println("---Test2 bean names : " + Arrays.asList(names));

        applicationContext.close();
    }
}

Запустите весенний загрузочный класс, и консоль выведет результат:

---hasTestBean : true ---Test bean names : [test] ---Test2 bean names : [test2]

Из результатов видно, что Тест нормально регистрируется в пружинном контейнере, что удовлетворяетTestConditionУсловия интерфейса, все Test2 также зарегистрированы в контейнере spring, для дальнейшей проверки результатов мы удаляем компонент Test, оставляем только регистрацию Test2, модифицируем класс конфигурации следующим образом

/**
 * spring组件配置类,将Test组件删除掉
 */
@Configuration
public class TestConfiguration {
    /**
     * 根据TestCondition接口的条件判断向spring容器中注册Test2组件
     */
    @Bean
    @Conditional(TestCondition.class)
    public Test2 test2() {
        return new Test2();
    }
}

Перезапустите весенний загрузочный класс, и консоль выведет результат следующим образом:

---hasTestBean : false ---Test bean names : [] ---Test2 bean names : []

Видно, что когда класс Test не зарегистрирован в контейнере spring, он не удовлетворяетсяTestConditionУсловия интерфейса, поэтому компонент Test2 не будет зарегистрирован в контейнере Spring. На данный момент, если test2() зарегистрирован в компоненте@ConditionalКогда компонент удален, компонент Test2 будет нормально зарегистрирован в контейнере Spring.

В приведенном выше примере@ConditionalАннотация добавляется к методу. В настоящее время условие действует только для текущего метода.@ConditionalАннотации также могут быть добавлены кВыше условие справедливо для регистрации компонента во всем классе. В соответствии с приведенным выше случаем выполните следующие настройки:

  • TestConditionнеобходимо реализоватьConfigurationConditionИнтерфейс для обработки классов конфигурации

Когда аннотация @Conditional добавляется в класс конфигурации, следует отметить, что условие в интерфейсе Condition предназначено для управления самим классом конфигурации или всеми компонентами в классе конфигурации, поэтому Spring Framework предоставляет интерфейс ConfigurationCondition и использует значение перечисления чтобы позволить нам пользовательский выбор.

enum ConfigurationPhase {

	/**
	 * Condition接口中的条件控制着配置类本身的注册,当条件不匹配时,不会添加@configuration类
	 */
	PARSE_CONFIGURATION,

	/**
	 * 控制Condition接口中的条件是对配置类中的组件进行解析,不会影响配置类本身的注册
	 */
	REGISTER_BEAN
}
  • TestConditionРеализация интерфейса изменена следующим образом
public class TestCondition implements ConfigurationCondition {
    /**
     * 当IOC容器中包含 Test的bean定义信息时,条件成立
     *
     * @param context  条件判断的上下文环境
     * @param metadata 正在检查的类或方法的元数据
     * @return 条件是否成立
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
        boolean hasTestBean = ArrayUtils.isNotEmpty(testBeanNames);
        System.out.println("---hasTestBean : " + hasTestBean);
        return hasTestBean;
    }

    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }
}
  • Внесите изменения в классы javaBean и классы конфигурации.
@Data
@Component // 通过@Component注解直接注册 Test 组件
public class Test {
    private String id = "@Bean";
}

@Data
public class Test2 {
    private String id = "Test2: @Conditional";
}

@Data
public class Test3 {
    private String id = "Test3: @Conditional";
}
  • Класс конфигурации настраивается следующим образом, добавьте класс конфигурации@Conditionalаннотация
/**
 * spring组件配置类,根据TestCondition接口的条件判断向spring容器中注册Test2,Test3组件
 */
@Configuration
@Conditional(TestCondition.class)
public class TestConfiguration {
    @Bean
    public Test3 test3() {
        return new Test3();
    }

    @Bean
    public Test2 test2() {
        return new Test2();
    }
}
  • Добавить пару в класс начальной загрузкиTest3тип запроса
String[] names = applicationContext.getBeanNamesForType(Test3.class);
System.out.println("---Test3 bean names : " + Arrays.asList(names));

Запустите класс начальной загрузки, и консоль выведет результат следующим образом:

---hasTestBean : true ---Test bean names : [test] ---Test2 bean names : [test2] ---Test3 bean names : [test3]

Отсюда видно, что когдаTestConditionПри совпадении условий интерфейса Test2 и Test3 регистрируются в контейнере Spring, если компонент Test не зарегистрирован, мы увидим следующие результаты.

  • БудуTestсорт@ComponentКомментарий удаляется, а остальной код остается без изменений
@Data
public class Test {
    private String id = "@Bean";
}

Перезапустите класс начальной загрузки и распечатайте результат следующим образом:

---hasTestBean : false ---Test bean names : [] ---Test2 bean names : [] ---Test3 bean names : []

Отсюда видно, чтоTestConditionУсловие управляет регистрацией компонента в классе конфигурации

Примечания по использованию @Conditional

  • когда@ConditionalКогда аннотация добавляется к методу, ее можно использовать напрямую.ConditionИнтерфейс реализован, и условие соответствует, чтобы определить, может ли компонент быть зарегистрирован в контейнере Spring.
  • когда@ConditionalКогда аннотация добавляется в класс конфигурации, вам нужно использоватьConfigurationConditionИнтерфейс реализованConfigurationPhaseНастроен в соответствии с влиянием самого класса, зарегистрированного с указанными критериями. потому чтоConditionСостояние интерфейса выполняется в процессе весеннего сканирования компонентов-кандидатов, поэтому нужно обратить внимание на эту проблему при оценке состояния по бину. Если это индивидуальное суждение о бизнес-требованиях, это не повлияет на него.

Суммировать

Когда мы обычно используем Spring или Spring MVC, использование аннотации @Conditional может быть немногочисленным, но Spring Boot сейчас популярен, и Spring Boot значительно расширил @Conditional, поэтому понимание использования и принципа @Conditional также является важным. хорошая идея Базовое изучение Spring Boot делает больше предзнаменований. В этой статье рассказывается об использовании @Conditional, но не объясняется подробно принцип Condition, который будет объяснен в последующем процессе сканирования компонентов Spring.

Учиться никогда не бывает легко, вы можете запутаться, вы можете быть ленивым, но вы никогда не сможете перестать двигаться вперед.

Если вы не наберете несколько шагов, вы не сможете пройти тысячу миль, если вы не наберете небольшой ручей, вы не сможете создать реку или море;

черные глаза の личный блог