这类注解都不知道,还好意思说会Spring Boot ?

前言

不知道大家在使用Spring Boot开发的日常中有没有用过@Conditionalxxx注解,比如@ConditionalOnMissingBean。相信看过Spring Boot源码的朋友一定不陌生。

@Conditionalxxx这类注解表示某种判断条件成立时才会执行相关操作。掌握该类注解,有助于日常开发,框架的搭建。

今天这篇文章就从前世今生介绍一下该类注解。

Spring Boot 版本

本文基于的Spring Boot的版本是2.3.4.RELEASE。

@Conditional

@Conditional注解是从Spring4.0才有的,可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被Spring容器处理。

@Conditional的使用很广,比如控制某个Bean是否需要注册,在Spring Boot中的变形很多,比如@ConditionalOnMissingBean、@ConditionalOnBean等等,如下:

这类注解都不知道,还好意思说会Spring Boot ?

该注解的源码其实很简单,只有一个属性value,表示判断的条件(一个或者多个),是org.springframework.context.annotation.Condition类型,源码如下:

@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition} classes that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }

@Conditional注解实现的原理很简单,就是通过org.springframework.context.annotation.Condition这个接口判断是否应该执行操作。

Condition接口

@Conditional注解判断条件与否取决于value属性指定的Condition实现,其中有一个matches()方法,返回true表示条件成立,反之不成立,接口如下:

@FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }

matches中的两个参数如下:

context:条件上下文,ConditionContext接口类型的,可以用来获取容器中上下文信息。

metadata:用来获取被@Conditional标注的对象上的所有注解信息

ConditionContext接口

这个接口很重要,能够从中获取Spring上下文的很多信息,比如ConfigurableListableBeanFactory,源码如下:

public interface ConditionContext { /** * 返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息 */ BeanDefinitionRegistry getRegistry(); /** * 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象 */ @Nullable ConfigurableListableBeanFactory getBeanFactory(); /** * 返回当前spring容器的环境配置信息对象 */ Environment getEnvironment(); /** * 返回资源加载器 */ ResourceLoader getResourceLoader(); /** * 返回类加载器 */ @Nullable ClassLoader getClassLoader(); } 如何自定义Condition?

举个栗子:假设有这样一个需求,需要根据运行环境注入不同的Bean,Windows环境和Linux环境注入不同的Bean。

实现很简单,分别定义不同环境的判断条件,实现org.springframework.context.annotation.Condition即可。

windows环境的判断条件源码如下

/** * 操作系统的匹配条件,如果是windows系统,则返回true */ public class WindowsCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { //获取当前环境信息 Environment environment = conditionContext.getEnvironment(); //获得当前系统名 String property = environment.getProperty("os.name"); //包含Windows则说明是windows系统,返回true if (property.contains("Windows")){ return true; } return false; } }

Linux环境判断源码如下

/** * 操作系统的匹配条件,如果是windows系统,则返回true */ public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { Environment environment = conditionContext.getEnvironment(); String property = environment.getProperty("os.name"); if (property.contains("Linux")){ return true; } return false; } }

配置类中结合@Bean注入不同的Bean,如下

@Configuration public class CustomConfig { /** * 在Windows环境下注入的Bean为winP * @return */ @Bean("winP") @Conditional(value = {WindowsCondition.class}) public Person personWin(){ return new Person(); } /** * 在Linux环境下注入的Bean为LinuxP * @return */ @Bean("LinuxP") @Conditional(value = {LinuxCondition.class}) public Person personLinux(){ return new Person(); }

简单的测试一下,如下

@SpringBootTest class SpringbootInterceptApplicationTests { @Autowired(required = false) @Qualifier(value = "winP") private Person winP; @Autowired(required = false) @Qualifier(value = "LinuxP") private Person linP; @Test void contextLoads() { System.out.println(winP); System.out.println(linP); } }

Windows环境下执行单元测试,输出如下

com.example.springbootintercept.domain.Person@885e7ff null

很显然,判断生效了,Windows环境下只注入了WINP。

条件判断在什么时候执行?

条件判断的执行分为两个阶段,如下:

配置类解析阶段(ConfigurationPhase.PARSE_CONFIGURATION):在这个阶段会得到一批配置类的信息和一些需要注册的Bean。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypswj.html