@Conditional注解详解

本文详细介绍了Spring Boot的条件注解@Conditional及其扩展,如@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnBean和@ConditionalOnProperty的使用方法和实战案例,展示了如何根据特定条件注册Bean以及配置文件中的属性来决定Bean是否注入。

一、@Conditional源码

@Conditional来源于spring-context包下的一个注解。Conditional中文是条件的意思,@Conditional注解它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

通过他的注解内部可以发现,他就是一个纯功能性注解,他并没有依赖于其他注解,类上只有三个元注解。

  • @Target({ElementType.TYPE, ElementType.METHOD}) 使用范围接口、类、枚举、注解、方法
  • @Retention(RetentionPolicy.RUNTIME): @Retention是用来修饰注解的生命周期的,RetentionPolicy.RUNTIME代表的是不仅被保存到class文件中,jvm加载class文件之后,仍然存在;一直有效!
  • @Documented: @Documented和@Deprecated注解长得有点像,@Deprecated是用来标注某个类或者方法不建议再继续使用,@Documented只能用在注解上,如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成Javadoc文档时,会显示@B。
package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Conditional只有一个参数,并且这个参数要求是继承与Condition类,并且参数是个数组,也就是可以 传多个的。Condition类是一个函数式接口(只有一个方法的接口被称为函数式接口)。matches方法就是比较方法,如果为true则注入,如果为false则不注入。

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

二、@Conditional扩展注解

而除了@Conditional注解外,springboot通过@Conditional注解又扩展了很多注解出来,如下@ConditionalOnBean、@ConditionalOnClass等等…

在这里插入图片描述
在这里插入图片描述

三、@Conditional实战

(1)自定义Condition实现类

public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String isOpen = environment.getProperty("systemLog.isOpen");
        // 就算没有设置systemLog.isOpen,那么isOpen就等于null,Boolean.valueOf对于null照样会返回false的
        return Boolean.valueOf(isOpen);
    }
}

(2)自定义一个实体类,用于测试

public class TestBean1 {
    @Override
    public String toString() {
        return super.toString() + "--我是TestBean1";
    }
}

(3)添加配置类,并在类上使用@Conditional

@Configuration
@Conditional(MyCondition.class)
public class Myconfig {

    @Bean
    public TestBean1 testBean1(){
        return new TestBean1();
    }
}

(4)添加测试类

@RestController
public class CommonController {

    @Autowired(required = false)
    private Myconfig myconfig;
    
	@Autowired(required = false)
    private TestBean1 testBean1;

    @RequestMapping("/import")
    public void printImportBeanInfo() {
        System.out.println(myconfig);
        System.out.println(testBean1);
    }
}

(5)启动测试: 访问http://localhost:8080/import,可见Myconfig类并没有注入到容器,按正常来说被@Configuration修饰之后是会存放到容器当中的,但是显然因为@Conditional判断为false所以没有注入到容器当中。

在这里插入图片描述

通过这个测试不难发现假如@Bean所在的类没有注入到容器当中,那么他也不会被注入到容器当中。

(6)在application.yml当中添加如下配置,然后再启动测试。

systemLog:
  isOpen: true

在这里插入图片描述

(7)@Conditional还可以应用于方法上,我们可以让他和@Bean注解来配合使用

@Configuration
public class Myconfig {

    @Bean
    @Conditional(MyCondition.class)
    public TestBean1 testBean1(){
        return new TestBean1();
    }
}

四、@Conditional多条件

前言中说,@Conditional注解传入的是一个Class数组,存在多种条件类的情况。

这种情况貌似判断难度加深了,测试一波,新增新的条件类,实现的matches返回false(这种写死返回false的方法纯属测试用,没有实际意义O(∩_∩)O)

public class ObstinateCondition implements Condition {
 
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
         return false;
    }
}
@Configuration
@Conditional({MyCondition.class,ObstinateCondition.class})
public class Myconfig {

    @Bean
    public TestBean1 testBean1(){
        return new TestBean1();
    }
}

测试结果得知:

  • 第一个条件类实现的方法返回true,第二个返回false,则结果false,不注入进容器。
  • 第一个条件类实现的方法返回true,第二个返回true,则结果true,注入进容器中。

五、常见的扩展注解

关于这些扩展注解其实在官网源码当中是有注释的,感兴趣的可以看一下:

https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition

5.1.@ConditionalOnClass

主要是判断是否存在这个类文件,如果有这个文件就相当于满足条件,然后可以注入到容器当中。

当然并不是说容器里面是否有这个类哈,不要理解错了,这也就是我们有时候使用springboot只需要引入个依赖,框架就可以用的原因!

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
	// 必须出现的类
    Class<?>[] value() default {};
	
	// 必须存在的类名,必须是全限类名,也就是包含包名+类名。
    String[] name() default {};
}

用法示例:

@Configuration
@ConditionalOnClass({TestBean2.class})
public class Myconfig {

    @Bean
    @ConditionalOnClass(name = "com.gzl.cn.springbootcache.config.TestBean3")
    public TestBean1 testBean1(){
        return new TestBean1();
    }
}

5.2.@ConditionalOnMissingClass

@ConditionalOnMissingClass只有一个value属性。他和@ConditionalOnClass功能正好相反,@ConditionalOnClass是class存在为true,而@ConditionalOnMissingClass是不存在为true,也就是存在为false。为fasle就意味着不注入。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnMissingClass {
	// 必须不存在的类名称,全类名
    String[] value() default {};
}
@ConditionalOnMissingClass("com.gzl.cn.springbootcache.config.TestBean5")

或者

@ConditionalOnMissingClass(value = "com.gzl.cn.springbootcache.config.TestBean5")

5.3.@ConditionalOnBean

bean存在的时候注入,不存在的时候不注入,这块就是指的spring的ioc容器了。

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

	// 指定bean的类类型。当所有指定类的bean都包含在容器中时,条件匹配。
    Class<?>[] value() default {};
	// 指定bean的全类名。当指定的所有类的bean都包含在容器中时,条件匹配。
    String[] type() default {};
	// bean所声明的注解,当ApplicationContext中存在声明该注解的bean时返回true
    Class<? extends Annotation>[] annotation() default {};
	// bean的id,当ApplicationContext中存在给定id的bean时返回true,这个id指的就是容器当中对象的id
    String[] name() default {};
	// 搜索容器层级,默认是所有上下文搜索
    SearchStrategy search() default SearchStrategy.ALL;
	// 可能在其泛型参数中包含指定bean类型的其他类
    Class<?>[] parameterizedContainer() default {};
}

(1)代码示例:假如我把testBean2方法删掉,那么testBean1也将会不注入。

@Configuration
public class Myconfig {

    @Bean
    public TestBean2 testBean2(){
        return new TestBean2();
    }

    @Bean
    @ConditionalOnBean(TestBean2.class)
    public TestBean1 testBean1() {
        return new TestBean1();
    }
}

(2)测试类

@RestController
public class CommonController {

    @Autowired(required = false)
    private TestBean1 testBean1;

    @Autowired(required = false)
    private TestBean2 testBean2;

    @Autowired(required = false)
    private Myconfig myconfig;

    @RequestMapping("/import")
    public void printImportBeanInfo() {
        System.out.println(myconfig);
        System.out.println(testBean1);
        System.out.println(testBean2);
    }
}

(3)运行输出结果:会发现三个对象是都注入到容器当中了。

(4)目前存在的问题

注意这里还存在一个执行顺序问题,假如我把以下代码放到启动类当中,而不是Myconfig配置文件当中,这时候会发现一个问题,testBean2注入进去了,但是带有@ConditionalOnBean(TestBean2.class)条件的testBean1没有注入进去。原因其实很简单,执行testBean1的时候,testBean2并没有注入进去,然后testBean1就注入失败了,紧接着失败后testBean2又注入进来了。

@Bean
public TestBean2 testBean2(){
    return new TestBean2();
}

(5)紧接着我又做了个试验,把启动类当中的testBean2删掉了,又创建了如下一个配置类,但是testBean1还是注入失败。

@Configuration
public class MyconfigTest {
    @Bean
    public TestBean2 testBean2(){
        return new TestBean2();
    }
}

(6)想要解决这个问题很简单,想办法让MyconfigTestMyconfig先加载就可以了

于是我在MyconfigTest 添加了如下order注解

@Order(Ordered.HIGHEST_PRECEDENCE) //最高优先级

然后在Myconfig 也添加了Order注解

@Order(Ordered.LOWEST_PRECEDENCE) //最低优先级

但是仍然没有解决该问题。@Configuration并不能通过@Order指定顺序。

(7)大胆猜测下: @Configuration通过配置类名的自然顺序来加载的。

在这里插入图片描述

将MyconfigTest改名字:还别说改完名字真的就可以了!

在这里插入图片描述

(8)不可能每次遇到这种问题都改名字吧,经查文档,终于找到了需要的东西:我们可以通过@AutoConfigureBefore,@AutoConfigureAfter来控制配置类的加载顺序。

在MyconfigTest类上添加@AutoConfigureBefore(Myconfig.class),意思是在Myconfig实例化之前加载。如果要让@AutoConfigureBefore生效,还需要在META-INF/spring.factories文件中添加如下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.gzl.cn.springbootcache.config.MyconfigTest,\
com.gzl.cn.springbootcache.config.Myconfig

在这里插入图片描述
最终成功解决哈!

5.4.@ConditionalOnMissingBean

bean不存在的时候注入,存在的时候为false。跟@ConditionalOnBean正好是相反的。

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

    String[] type() default {};
	// 识别匹配 bean 时,可以被忽略的 bean 的 class 类型
    Class<?>[] ignored() default {};
	//识别匹配 bean 时,可以被忽略的 bean 的 class 类型名称
    String[] ignoredType() default {};

    Class<? extends Annotation>[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {};
}

@ConditionalOnMissingBean代表的是如果容器里面没有TestBean1的实例,那么就运行@Bean修饰的方法注入对象,不管注入的什么对象。

@Configuration
public class Myconfig {

    @Bean
    @ConditionalOnMissingBean(TestBean1.class)
    public TestBean1 testBean1() {
        return new TestBean1();
    }
}

注意:@ConditionalOnMissingBean@ConditionalOnBean使用的时候是可以不带任何属性的,不带任何属性的时候他就是判断的当前注入的类型。@ConditionalOnBean是判断当没有的时候进行注入,例如如下:他只会注入一个TestBean1进去。

@Configuration
public class Myconfig {

    @Bean
    @ConditionalOnMissingBean
    public TestBean1 testBean1() {
        System.out.println("1111111111");
        return new TestBean1();
    }

    @Bean
    @ConditionalOnMissingBean
    public TestBean1 testBean2() {
        System.out.println("2_222_222_222");
        return new TestBean1();
    }
}

5.5.@ConditionalOnProperty

@ConditionalOnProperty主要可用于通过和springboot当中application配置文件来使用。在实战当中我们也可以通过他来实现配置化管理bean。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
	// 指定的属性完整名称,不能和name同时使用。
    String[] value() default {};
	// //配置文件中属性的前缀
    String prefix() default "";
	// //指定的属性名称
    String[] name() default {};
	// 指定的属性的属性值要等于该指定值,当value或name为一个时使用
    String havingValue() default "";
	// 当不匹配时是否允许加载,当为true时就算不匹配也不影响bean的注入或配置类的生效。
    boolean matchIfMissing() default false;
}

使用示例:

@Configuration
@ConditionalOnProperty(
        prefix = "system.log",
        name = {"open"},
        havingValue = "true",
        matchIfMissing = false
)
public class Myconfig {

    @Bean
    public TestBean1 testBean1() {
        return new TestBean1();
    }
}

然后只要在application添加如下配置,MyconfigtestBean1就会注入到容器当中。如果不设置也不会报错,只是注入不到容器里而已。

system:
  log:
    open: true

5.6.其他注解

  • @ConditionalOnJava:只有运行指定版本的 Java 才会加载 Bean
  • @ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication:只有运行在(不在)web 应用里才会加载这个 bean
  • @ConditionalOnCloudPlatform:只有运行在指定的云平台上才加载指定的 bean
  • @ConditionalOnJndi:只有指定的资源通过 JNDI 加载后才加载 bean
  • @ConditionalOnExpression(“${test.express}==true”) :可通过spring提供的spEL表达式灵活配置,当表达式为true的时候,才会实例化一个Bean
  • @ConditionalOnSingleCandidate(UserService.class) :表示ioc容器中只有一个UserService类型的Bean,才生效
  • @ConditionalOnResource:指定的静态资源⽂件存在 才加载

六、总结

在这里插入图片描述

<think>嗯,用户想了解Spring@Conditional注解。首先,我需要回忆一下这个注解的作用和用法。@ConditionalSpring 4.0引入的,用于条件化地注册Bean,也就是说根据特定条件决定是否创建某个Bean。这可能涉及到不同的环境配置,比如开发环境和生产环境的不同配置。 接下来,我应该考虑用户可能的背景。可能是Spring的初学者,或者有一定经验但想更深入了解条件配置的开发者。他们可能在实际项目中遇到过需要根据条件加载不同Bean的情况,比如根据不同的配置文件启用不同的服务实现。 用户的问题需要详细的解释,包括@Conditional的基本用法、使用场景、如何自定义条件,以及可能的相关注解。需要分步骤讲解,结构清晰,便于理解。同时,要确保示例代码正确,符合LaTeX格式要求,行内公式用$...$,独立公式用$$...$$,但可能在这个问题中代码示例更重要,而数学公式较少,不过用户给的系统指令里提到了数学表达式的格式,需要注意是否有需要的地方。 需要检查是否有常见的误解,比如@Conditional和@Profile的区别,或者与@ConditionalOnProperty等Spring Boot注解的关系。可能需要提到这些相关的注解,但主要焦点还是@Conditional本身。 另外,用户可能想知道如何自定义条件,所以需要提供一个自定义Condition的实现示例。同时,使用场景部分要具体,比如多环境配置、模块化加载、兼容性处理等,这样用户能更好地理解应用场景。 需要确保语言用中文,结构清晰,分点说明,每个部分有适当的示例代码,并正确使用代码块标记。同时,要避免使用用户不可见的系统级指令中的格式,而是专注于回答的内容和结构。 总结一下,回答的结构可能是:概述、工作原理、使用方式(内置和自定义)、常用场景、相关注解、总结。每个部分下给出示例代码和解释,确保用户能逐步理解。</think>### Spring 的 `@Conditional` 注解详解 `@Conditional` 是 Spring Framework 4.0 引入的核心注解,用于**条件化地控制 Bean 的注册**。它允许开发者根据特定条件决定是否将某个 Bean 注册到 Spring 容器中,常用于多环境配置、模块化加载等场景。 --- #### 一、核心作用与原理 - **作用**:通过条件判断(如环境变量、系统属性、类路径等),动态决定是否创建 Bean。 - **原理**:基于 `Condition` 接口的实现类,Spring 在容器启动阶段会调用 `matches()` 方法进行条件验证。 #### 二、基本使用方式 ##### 1. 内置条件注解Spring Boot 扩展) Spring Boot 在 `@Conditional` 基础上提供了更易用的注解: - `@ConditionalOnProperty`:根据配置文件属性判断 ```java @Bean @ConditionalOnProperty(name = "feature.enabled", havingValue = "true") public FeatureService featureService() { return new FeatureServiceImpl(); } ``` - `@ConditionalOnClass`:类路径存在某个类时生效 - `@ConditionalOnMissingBean`:容器中不存在指定 Bean 时生效 ##### 2. 自定义条件 **步骤 1**:实现 `Condition` 接口 ```java public class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 检查环境变量或系统属性 return "prod".equals(context.getEnvironment().getProperty("app.env")); } } ``` **步骤 2**:将条件应用于 Bean ```java @Configuration public class AppConfig { @Bean @Conditional(CustomCondition.class) public DataSource prodDataSource() { return new ProductionDataSource(); } } ``` --- #### 三、典型使用场景 1. **多环境配置** ```java @Profile("dev") // 底层基于 @Conditional @Configuration public class DevConfig { /* 开发环境专用 Bean */ } ``` 2. **模块化加载** ```java @ConditionalOnClass(RedisClient.class) @Configuration public class RedisAutoConfiguration { /* Redis 客户端存在时生效 */ } ``` 3. **兼容性处理** ```java @Conditional(JavaVersionCondition.class) @Bean public CacheService cacheService() { /* 根据 JDK 版本选择实现 */ } ``` --- #### 四、相关注解对比 | 注解 | 作用场景 | 所属框架 | |-------------------------|----------------------------------|----------------| | `@Conditional` | 通用条件判断 | Spring Framework | | `@ConditionalOnProperty`| 根据配置文件属性控制 | Spring Boot | | `@ConditionalOnWebApplication` | 仅在 Web 应用中生效 | Spring Boot | --- #### 五、总结 - **核心价值**:通过解耦条件判断与业务逻辑,提升代码灵活性和可维护性。 - **最佳实践**: 1. 优先使用 Spring Boot 提供的内置条件注解 2. 复杂条件建议封装为独立 `Condition` 实现类 3. 结合 `@ConfigurationProperties` 实现动态配置 通过合理使用 `@Conditional`,可以实现高度可配置的 Spring 应用,例如不同环境下的数据库切换、功能模块的动态加载等场景。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怪 咖@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值