zl程序教程

您现在的位置是:首页 >  后端

当前栏目

SpringBoot中的@Conditional注解

SpringBoot 注解 Conditional
2023-06-13 09:15:28 时间

SpringBoot中的@Conditional注解

一、介绍

Spring的应用下,我们希望一些bean可以通过一些条件来判断是否需要实例化,并加载到spring容器中。

所以,@Conditional注解就是为了解决上面这个需求而制定的注解。@Conditional注解是总接口,可以定制逻辑。

二、详情

1)@Conditional

先看源码,此注解需要传入Condition接口的实现类,可以多个

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    
	Class<? extends Condition>[] value();
    
}

所以,使用此注解时,我们若是有些高度定制化的一些判断,可以先实现Condition接口,再讲实现类提供给@Conditional注解,使用示例如下。

首先要有一个Condition接口的实现类,可以看看框架中其他的实现类,这边自己实现一个

package com.banmoon.test;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class TestCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // TODO 定制化逻辑,比如说数据库的某项配置
        return true;
    }
}

再进行其进行判断并初始化bean

@SpringBootApplication
public class TestApplication {

    @Bean("conditional")
    @Conditional(TestCondition.class)
    public String conditional() {
        return "conditional";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        String string = run.getBean("conditional", String.class);
        System.out.println(string);
        System.out.println(System.lineSeparator());
    }
}

运行结果如下,成功获取到bean

2)@ConditionalOnBean

@ConditionalOnBean@ConditionalOnMissingBean是相反对应的一组注解,看注解名称也可以看出来。

前者是判断存在某个bean,后者是判断是否确实某个bean

先来看看他们的使用,如下

@SpringBootApplication
public class TestApplication {

    @Bean("conditionalOnBean")
    @ConditionalOnBean(TestApplication.class)
    public String conditionalOnBean() {
        return "conditionalOnBean";
    }

    @Bean("conditionalOnMissingBean")
    @ConditionalOnMissingBean(TestApplication.class)
    public String conditionalOnMissingBean() {
        return "conditionalOnMissingBean";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

可以看到只有一个bean被创建出来,另外一个由于不满足条件,所以没有创建bean


查看注解源码,发现除了可以传Class对象,还可以传其他的属性来进行确定

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

	/**
	 * 检查bean的class类型,当指定的所有类的 bean 都包含在 BeanFactory 中时,此条件才匹配
	 */
	Class<?>[] value() default {};

	/**
	 * 检查bean的权限名,当指定的所有类的 bean 都包含在 BeanFactory 中时,此条件才匹配
	 */
	String[] type() default {};

	/**
	 * 检查bean的注解类型,当指定的所有注解都在 BeanFactory 中的 bean 上定义时,此条件才匹配
	 */
	Class<? extends Annotation>[] annotation() default {};

	/**
	 * 要检查的 bean 的名称。当指定的所有 bean 名称都包含在 BeanFactory 中时,此条件才匹配。
	 */
	String[] name() default {};

	/**
	 * 考虑应用程序的上下文结构的策略
	 */
	SearchStrategy search() default SearchStrategy.ALL;

	/**
	 * 可能在其通用参数中包含指定 bean 类型的其他类。例如,声明 value=Name.class 和 parameterizedContainer=NameRegistration.class 的注释将同时检测 Name 和 NameRegistration<Name>。
	 */
	Class<?>[] parameterizedContainer() default {};

}

3)@ConditionalOnProperty

看名字也就能看的出来,是指从配置文件加载,用来进行条件判断。使用如下

package com.banmoon.test;

import cn.hutool.core.util.StrUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.Map;

@SpringBootApplication
public class TestApplication {

    @Bean("conditionalOnProperty")
    @ConditionalOnProperty(value = "conditionalOnProperty", prefix = "banmoon-condition")
    public String conditionalOnProperty() {
        return "conditionalOnProperty";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

还是点入进去看看该注解的源码

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

	/**
	 * 同name属性一致
	 */
	String[] value() default {};

	/**
	 * 应用于每个属性的前缀。如果未指定,前缀会自动以点结尾。有效前缀由一个或多个用点分隔的单词定义(例如“acme.system.feature”)
	 */
	String prefix() default "";

	/**
	 * 要测试的属性的名称。如果已定义前缀,则将其应用于计算每个属性的完整键。例如,如果前缀是 app.config 并且一个值是 my-value,则完整的键将是 app.config.my-value 使用虚线表示法来指定每个属性,即全部小写,用“-”分隔单词(例如 my-long-property)
	 */
	String[] name() default {};

	/**
	 * 属性预期值的字符串表示形式。如果未指定,则该属性不得等于 false
	 */
	String havingValue() default "";

	/**
	 * 如果未设置属性,则指定条件是否应匹配。默认为false
	 */
	boolean matchIfMissing() default false;

}

还有一段不好翻译,给予截图吧,指的是havingValue属性的使用。

这些比较特殊,不同的属性值和不同的havingValue组合,可以得到什么样的结果。

4)@ConditionalOnClass

@ConditionalOnClass@ConditionalOnMissingClass也是一组相对的注解,他们的功能是判断是否拥有java

@SpringBootApplication
public class TestApplication {

    @Bean("conditionalOnClass")
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    public String conditionalOnClass() {
        return "conditionalOnClass";
    }

    @Bean("conditionalOnMissingClass")
    @ConditionalOnMissingClass(value = "oracle.jdbc.driver.OracleDriver")
    public String conditionalOnMissingClass() {
        return "conditionalOnMissingClass";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

由于我只添加了MySQL的驱动,没有加上Oracle,所以结果如下

5)@ConditionalOnWebApplication

@ConditionalOnWebApplication@ConditionalOnNotWebApplication是一组相对的注解,指的判断是否是web环境应用。

package com.banmoon.test;

import cn.hutool.core.util.StrUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.Map;

@SpringBootApplication
public class TestApplication {

    @Bean("conditionalOnWebApplication")
    @ConditionalOnWebApplication
    public String conditionalOnWebApplication() {
        return "conditionalOnWebApplication";
    }

    @Bean("conditionalOnNotWebApplication")
    @ConditionalOnNotWebApplication
    public String conditionalOnNotWebApplication() {
        return "conditionalOnNotWebApplication";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

6)@ConditionalOnJava

@ConditionalOnJava用来判断当前java的版本

@SpringBootApplication
public class TestApplication {

    @Bean("conditionalOnJava")
    @ConditionalOnJava(value = JavaVersion.EIGHT)
    public String conditionalOnJava() {
        return "conditionalOnJava";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

看下注解的源码,比较简单

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {

	/**
	 * 配合value()使用,来确定范围。默认是Range.EQUAL_OR_NEWER,也就是比指定范围高
	 */
	Range range() default Range.EQUAL_OR_NEWER;

	/**
	 * 指定Java版本
	 */
	JavaVersion value();

	enum Range {

		/**
		 * 大于等于
		 */
		EQUAL_OR_NEWER,

		/**
		 * 低于
		 */
		OLDER_THAN

	}

}

7) @ConditionalOnExpression

使用Spring表达式来进行判断,也就是SpEL表达式

@SpringBootApplication
public class TestApplication {

    @Bean("intValue")
    public Integer intValue() {
        return 10;
    }

    @Bean("conditionalOnExpression")
    @ConditionalOnExpression("#{intValue>5}")
    public String conditionalOnExpression() {
        return "conditionalOnExpression";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

8)@ConditionalOnResource

@ConditionalOnResource是否有指定的静态资源,示例如下

@SpringBootApplication
public class TestApplication {

    @Bean("conditionalOnResource")
    @ConditionalOnResource(resources = "application.yml")
    public String conditionalOnResource() {
        return "conditionalOnResource";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

9)@ConditionalOnSingleCandidate

@ConditionalOnSingleCandidate,判断指定的类型是否只有一个bean,示例如下

@SpringBootApplication
public class TestApplication {

    @Bean("intValue")
    public Integer intValue(){
        return 10;
    }

    @Bean("intValue2")
    public Integer intValue2(){
        return 20;
    }

    @Bean("doubleValue")
    public Double doubleValue(){
        return 10D;
    }

    @Bean("intValueConditional")
    @ConditionalOnSingleCandidate(Integer.class)
    public String intValueConditional() {
        return "intValueConditional";
    }

    @Bean("doubleValueConditional")
    @ConditionalOnSingleCandidate(Double.class)
    public String doubleValueConditional() {
        return "doubleValueConditional";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
        Map<String, String> beanMap = run.getBeansOfType(String.class);
        beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
        System.out.println(System.lineSeparator());
    }

}

三、最后

整理一下注解表格,其中有一些上面没有列出来的注解

注解

说明

@Conditional

传入一个Condition接口的实现类,来进行判断

@ConditionalOnBean

判断是否存在某个bean

@ConditionalOnMissingBean

判断是否缺失了某个bean

@ConditionalOnProperty

判断配置文件中的某项配置

@ConditionalOnClass

判断是否拥有某个java类

@ConditionalOnMissingClass

判断是否缺失了某个java类

@ConditionalOnWebApplication

判断当前是否为web环境

@ConditionalOnNotWebApplication

判断当前是否不为web环境

@ConditionalOnJava

判断当前java运行版本

@ConditionalOnExpression

使用Spring表达式来进行判断,也就是SpEL表达式

@ConditionalOnResource

判断是否有指定的静态资源

@ConditionalOnSingleCandidate

判断指定的类型是否只有一个bean

@ConditionalOnJndi

通过JNDI进行判断

@ConditionalOnCloudPlatform

判断当前环境是否是云平台

@ConditionalOnWarDeployment

判断当前是否是War包环境

@ConditionalOnMissingFilterBean

判断是否缺失了某个bean过滤器

我是半月,祝你幸福!!!