Spring Boot中的那些条件判断的实现方法

作者:沈子平 时间:2023-04-26 15:02:07 

Spring Boot中的那些Conditional

spring boot中为我们提供了丰富的Conditional来让我们得以非常方便的在项目中向容器中添加Bean。本文主要是对各个注解进行解释并辅以代码说明其用途。

所有ConditionalOnXXX的注解都可以放置在class或是method上,如果方式在class上,则会决定该class中所有的@Bean注解方法是否执行。

@Conditional

下面其他的Conditional注解均是语法糖,可以通过下面的方法自定义ConditionalOnXXX

Conditional注解定义如下,接收实现Condition接口的class数组。


public @interface Conditional {
 Class<? extends Condition>[] value();
}

而Condition接口只有一个matchs方法,返回是否匹配的结果。


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

通过操作系统进行条件判断,从而进行Bean配置。当Window时,实例化Bill的Person对象,当Linux时,实例化Linus的Person对象。


//LinuxCondition,为方便起见,去掉判断代码,直接返回true了
public class LinuxCondition implements Condition {
 @Override
 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
   return true;
 }
}

//WindowsCondition,为方便起见,去掉判断代码,直接返回false了
public class WindowsCondition implements Condition {
 @Override
 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
   return false;
 }
}
@Data

@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private String name;
 private Integer age;
}

//配置类
@Configuration
public class BeanConfig {

@Bean(name = "bill")
 @Conditional({WindowsCondition.class})
 public Person person1(){
   return new Person("Bill Gates",62);
 }

@Bean("linus")
 @Conditional({LinuxCondition.class})
 public Person person2(){
   return new Person("Linus",48);
 }
}

public class AppTest {
 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);

@Test
 public void test(){
   String osName = applicationContext.getEnvironment().getProperty("os.name");
   System.out.println("当前系统为:" + osName);
   Map<String, Person> map = applicationContext.getBeansOfType(Person.class);
   System.out.println(map);
 }
}

输出的结果:

当前系统为:Mac OS X
{linus=Person(name=Linus, age=48)}

@ConditionalOnBean & @ConditionalOnMissingBean

这两个注解会对Bean容器中的Bean对象进行判断,使用的例子是配置的时候,如果发现如果没有Computer实例,则实例化一个备用电脑。


@Data
@AllArgsConstructor
@ToString
public class Computer {
 private String name;
}

@Configuration
public class BeanConfig {
 @Bean(name = "notebookPC")
 public Computer computer1(){
   return new Computer("笔记本电脑");
 }

@ConditionalOnMissingBean(Computer.class)
 @Bean("reservePC")
 public Computer computer2(){
   return new Computer("备用电脑");
 }
}


public class TestApp {
 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
 @Test
 public void test1(){
   Map<String,Computer> map = applicationContext.getBeansOfType(Computer.class);
   System.out.println(map);
 }
}

修改BeanConfig,如果注释掉第一个@Bean,会实例化备用电脑,否则就不会实例化备用电脑

@ConditionalOnClass & @ConditionalOnMissingClass

这个注解会判断类路径上是否有指定的类,一开始看到的时候比较困惑,类路径上如果没有指定的class,那编译也通过不了啊...这个主要用于集成相同功能的第三方组件时用,只要类路径上有该组件的类,就进行自动配置,比如spring boot web在自动配置视图组件时,是用Velocity,还是Thymeleaf,或是freemaker时,使用的就是这种方式。

例子是两套盔甲A(光明套装)和B(暗黑套装),如果A不在则配置B。


public interface Fighter {
 void fight();
}
public class FighterA implements Fighter {
 @Override
 public void fight() {
   System.out.println("使用光明套装");
 }
}
public class FighterB implements Fighter {
 @Override
 public void fight() {
   System.out.println("使用暗黑套装");
 }
}

Van是武士,使用套装进行战斗


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Van {
 private Fighter fighter;
 public void fight(){
   fighter.fight();
 }
}

VanConfigA/B实例化武士


@Configuration
@ConditionalOnClass({FighterA.class})
public class VanConfigA {
 @Primary
 @Bean
 public Van vanA(){
   return new Van(new FighterA());
 }
}
@Configuration
@ConditionalOnClass({FighterB.class})
public class VanConfigB {
 @Bean
 public Van vanB(){
   return new Van(new FighterB());
 }
}

测试类,默认情况,如果套装AB都在类路径上,两套都会加载,A会设置为PRIMARY,如果在target class中将FightA.class删除,则只会加载套装B。


@SpringBootApplication
public class TestApp implements CommandLineRunner {
 @Autowired
 private Van van;
 public static void main(String[] args) {
   SpringApplication.run(TestApp.class, args);
 }
 @Override
 public void run(String... args) throws Exception {
   //do something
   van.fight();
 }
}

另外,尝试将两个VanConfigA/B合并,将注解ConditionalOnClass放到方法上,如果删除一个套装就会运行出错。

@ConditionalOnExpress

依据表达式进行条件判断,这个作用和@ConditionalOnProperty大部分情况可以通用,表达式更灵活一点,因为可以使用SpEL。例子中会判断properties中test.enabled的值进行判断。BeanConfig分别对布尔,字符串和数字三种类型进行判断。数字尝试了很多其他的方式均不行,比如直接使用==,貌似配置的属性都会当成字符串来处理。


@Data
public class TestBean {
 private String name;
}

@Configuration
@ConditionalOnExpression("#{${test.enabled:true} }")
//@ConditionalOnExpression("'zz'.equalsIgnoreCase('${test.name2}')")
//@ConditionalOnExpression("new Integer('${test.account}')==1")
public class BeanConfig {
 @Bean
 public TestBean testBean(){
   return new TestBean("我是美猴王");
 }
}

@SpringBootApplication
public class TestAppCommand implements CommandLineRunner {
 @Autowired
 private TestBean testBean;

public static void main(String[] args) {
   SpringApplication.run(TestAppCommand.class, args);
 }

@Override
 public void run(String... args) throws Exception {
   System.out.println(testBean.getName());
 }
}

@ConditionalOnProperty

适合对单个Property进行条件判断,而上面的@ConditionalOnExpress适合面对较为复杂的情况,比如多个property的关联比较。这个例子也给了三种基本类型的条件判断,不过貌似均当成字符串就可以...


@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestBean {
 private String name;
}

@Configuration
@ConditionalOnProperty(prefix = "test", name="enabled", havingValue = "true",matchIfMissing = false)
//@ConditionalOnProperty(prefix = "test", name="account", havingValue = "1",matchIfMissing = false)
//@ConditionalOnProperty(prefix = "test", name="name1", havingValue = "zz",matchIfMissing = false)
public class BeanConfig {

@Bean
 public TestBean testBean(){
   return new TestBean("我是美猴王");
 }
}


@SpringBootApplication
public class TestAppCommand implements CommandLineRunner {
 @Autowired
 private TestBean testBean;
 public static void main(String[] args) {
   SpringApplication.run(TestAppCommand.class, args);
 }
 @Override
 public void run(String... args) throws Exception {
   System.out.println(testBean.getName());

}
}

@ConditionalOnJava

可以通过java的版本进行判断。


@Data
public class TestBean {
}

@Configuration
@ConditionalOnJava(JavaVersion.EIGHT)
public class BeanConfig {

@Bean
 public TestBean testBean(){
   return new TestBean();
 }
}


public class TestApp {
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
 @Test
 public void test(){
   Map<String,TestBean> map = context.getBeansOfType(TestBean.class);
   System.out.println(map);
 }
}

@ConditionalOnResource

通过指定的资源文件是否存在进行条件判断,比如判断ehcache.properties来决定是否自动装配ehcache组件。


@Data
public class TestBean {
}

@Configuration
@ConditionalOnResource(resources = "classpath:application.yml")
public class BeanConfig {

@Bean
 public TestBean testBean(){
   return new TestBean();
 }
}


public class TestApp {
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);

@Test
 public void test(){
   Map<String,TestBean> map = context.getBeansOfType(TestBean.class);
   System.out.println(map);
 }
}

@ConditionalOnSingleCandidate

这个还没有想到应用场景,条件通过的条件是:1 对应的bean容器中只有一个 2.对应的bean有多个,但是已经制定了PRIMARY。例子中,BeanB装配的时候需要看BeanA的装配情况,所以BeanBConfig要排在BeanAConfig之后.可以修改BeanAConfig,将@Primary注解去掉,或者把三个@Bean注解去掉,BeanB就不会实例化了。


@Data
@AllArgsConstructor
@NoArgsConstructor
public class BeanA {
 private String name;
}

@Configuration
public class BeanAConfig {

@Bean
 @Primary
 public BeanA bean1(){
   return new BeanA("bean1");
 }
 @Bean(autowireCandidate = false)
 public BeanA bean2(){
   return new BeanA("bean2");
 }
 //@Bean(autowireCandidate = false)
 public BeanA bean3(){
   return new BeanA("bean3");
 }
}


@Data
public class BeanB {
}

@Configuration
@AutoConfigureAfter(BeanAConfig.class)
@ConditionalOnSingleCandidate(BeanA.class)
public class BeanBConfig {

@Bean
 public BeanB targetBean(){
   return new BeanB();
 }
}


public class TestApp {
 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanAConfig.class, BeanBConfig.class);

@Test
 public void test(){
   Map<String,BeanA> map = context.getBeansOfType(BeanA.class);
   System.out.println(map);
   Map<String,BeanB> map2 = context.getBeansOfType(BeanB.class);
   System.out.println(map2);
 }
}

@ConditionalOnNotWebApplication & @ConditionalOnWebApplication

判断当前环境是否是Web应用。

来源:https://segmentfault.com/a/1190000018831198

标签:Spring,Boot,条件判断
0
投稿

猜你喜欢

  • 使用Mybatis-Plus时的SqlSessionFactory问题及处理

    2022-01-30 07:40:04
  • 使用fastjson中的JSONPath处理json数据的方法

    2021-12-14 09:09:58
  • JavaCV实战之调用摄像头基础详解

    2022-07-15 02:14:10
  • 解读List list=new ArrayList()是怎么回事

    2022-04-17 12:28:22
  • C#枚举类型与位域枚举Enum

    2023-03-02 06:52:27
  • SpringBoot项目读取外置logback配置文件的问题及解决

    2023-06-06 10:50:22
  • 详解SpringMVC如何进行数据回显

    2023-09-12 08:48:15
  • 浅谈Java中复制数组的方式

    2022-04-14 23:30:27
  • 深入理解Spring AOP

    2023-02-09 15:14:40
  • C#简单输出日历的方法

    2023-11-13 05:56:10
  • IDEA社区版下载安装流程详解(小白篇)

    2021-11-13 12:37:18
  • 工作中禁止使用Executors快捷创建线程池原理详解

    2021-11-24 20:55:48
  • spring mvc高级技术实例详解

    2022-10-11 12:50:15
  • idea 创建properties配置文件的步骤

    2023-11-18 18:03:30
  • 使用Java8 Stream流的skip + limit实现批处理的方法

    2023-11-29 06:17:39
  • C#数据类型转换(显式转型、隐式转型、强制转型)

    2021-11-24 13:44:25
  • 基于Mybatis plus 自动代码生成器的实现代码

    2023-11-24 10:40:51
  • Java JVM中线程状态详解

    2023-01-24 16:06:19
  • Java Ehcache缓存框架入门级使用实例

    2022-08-23 20:51:54
  • 使用SpringBoot发送邮件的方法详解

    2023-01-30 02:21:37
  • asp之家 软件编程 m.aspxhome.com