详解SpringBoot @Conditional相关条件注解

发布时间 2023-10-27 20:24:23作者: 会炒饭的蛋

Spring boot条件注解是@ContionalXXX相关的注解,表示当特定条件有效时,被修饰的配置类或配置方法才会生效。

条件注解可以用来修饰@Configuration类或@Bean方法等。

主要有以下行为:

  • 当Spring Boot检测到类加载路径包含某个框架时,会自动配置该框架的基础Bean.
  • 只有当开发者没配置某些Bean时,Spring Boot才会在容器中自动配置对应的Bean。
  • 只有当开发者配置了某些属性时,Spring Boot才会在容器中自动配置对应的Bean。

概述

Spring Boot的条件注解支持如下几类:

类条件注解:

  • @ConditionalOnClass:该注解指定的类存在时,被修饰的类或方法生效。可通过value或name属性指定它所要求存在的类,其中value属性值是被检查类的Class对象,name属性值是被检查类的字符串形式的全限定类名—既然是检查目标类是否存在,那么通常用name属性值居多。
  • @ConditionalOnMissingClass:该注解指定的类不存在时,被修饰的类或方法生效。value属性值只能是被检查类的字符串形式的全限定类名—既然要确保该类不存在,那么该类对应的Class通常也就不存在了。

Bean条件注解:

  • @ConditionalOnMissingBean:目标Bean不存在时,实例化Bean。type是类的全限定名,value是Bean的class类型
  • @ConditionalOnSingleCandidate:该注解相当于@ConditionalOnBean的增强版,它不仅要求被检查的Bean必须存在,而且只能有一个“候选者”—能满足byType依赖注入的条件。
  • @ConditionalOnBean:目标Bean存在时,实例化Bean
  • @ConditionalOnMissingFilterBean:相当于@ConditionalOnMissingBean的特殊版本,它专门用于检查容器中是否存在指定类型的javax.servlet.Filter,因此它只能通过value属性指定其要检查的Filter的类型。

属性条件注解(properties配置):

  • @ConditionalOnProperty:用于检查特定属性是否具有指定的属性值,然后根据条件实例化Bean。

资源条件注解:

  • @ConditionalOnResource:要求指定的资源必须存在,其修饰的配置类或方法才会生效。使用该注解时只需指定resources属性,该属性指定必须存在的资源。

Web应用条件注解:

  • @ConditionalOnWebApplication:当前应用是Web应用,其修饰的配置类或方法生效。其中type有3个值,ANY(任何web应用都生效),SERVLET(Spring MVC情况下生效),REACTIVE(Spring WebFlux情况下生效)
  • @ConditionalOnNotWebApplication:要求当前应用不是Web应用时,该注解修饰的配置类或方法才会生效。
  • @ConditionalOnWarDeployment:要求当前应用以传统WAR包方式被部署到Web服务器或应用服务器中时(不以独立Java程序的方式运行),该注解修饰的配置类或方法才会生效。
  • @ConditionalOnNotWarDeployment:要求当前应用以不是传统WAR包方式被部署到Web服务器或应用服务器中时,该注解修饰的配置类或方法才会生效。

SpEL条件表达式注解:

  • @ConditionalOnExpression:要求指定SpEL表达式的值为true时,其所修饰的配置类或方法才会生效。

其他条件注解:

  • @ConditionalOnCloudPlatform:要求应用被部署在特定云平台上,这样其修饰的配置类或方法才会生效。该注解可通过value属性指定它所要求的云平台,该value属性支持多个枚举值,详见CloudPlatform枚举类。
  • @ConditionalOnJava:对目标平台的Java版本进行检测,它既可要求目标平台的Java版本是某个具体的版本,也可要求其高于或低于某个版本。JavaVersion value:指定要求的Java版本。ConditionalOnJava.Range range:该属性支持EQUAL_OR_NEWER(大于或等于value属性指定的版本)和OLDER_THAN(小于value 属性指定的版本)两个枚举值。如果不指定该属性,则要求目标平台的Java版本必须是value属性所指定的版本。
  • @ConditionalOnJndi:要求指定JNDI必须存在,使用该注解时通过value属性指定要检查的JNDI。
  • @ConditionalOnRepositoryType:要求特定的Spring Data Repository被启用时,其修饰的配置类或方法才会生效。

示例

新建一个springboot-demo项目,springboot版本3.1.5,java版本17.仅包含spring-boot-starter-web和lombok。

项目启动类SpringbootDemoApplication:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootDemoApplication {

	public static void main(String[] args) {
		var ctx = SpringApplication.run(SpringbootDemoApplication.class, args);
		//获取MyConfig对应的配置类
		System.out.println(ctx.getBean("myConfig"));
		System.out.println(ctx.getBean("myBean"));
	}

}

此时运行项目,会报异常:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myConfig' available

新建MyConfig和MyBean类:
MyConfig:


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyConfig {

    @Bean
    public MyBean myBean(){
        return new MyBean();
    }
}

MyBean:

import lombok.Data;

@Data
public class MyBean {
    private String name;
}

此时运行SpringbootDemoApplication,正常启动无报错

@ConditionalOnClass

对MyConfig增加@ConditionalOnClass注解:


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

//仅仅当perry.demo.springbootdemo.config.MyService类存在时,该配置类生效
@ConditionalOnClass(name = "perry.demo.springbootdemo.config.MyService")
@Configuration(proxyBeanMethods = false)
public class MyConfig {

    @Bean
    public MyBean myBean(){
        return new MyBean();
    }
}

再次运行程序,报异常:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myConfig' available

因为@ConditionalOnClass要求指定的类必须存在。

新建MyService类:

package perry.demo.springbootdemo.config;

import lombok.Data;

@Data
public class MyService {
    private String name;
}

再次启动程序,不再报错。

@ConditionalOnMissingClass

当MyConfig类被@ConditionalOnMissingClass注解修饰时,不存在对应的目标类,才会实例化指定的Bean.

//仅仅当perry.demo.springbootdemo.config.MyService类不存在时,该配置类生效
@ConditionalOnMissingClass(value = "perry.demo.springbootdemo.config.MyService")
public class MyConfig {...}

@ConditionalOnBean

使用@ConditionalOnBean修饰MyConfig

//当存在MyService的Bean时,实例化MyConfig
@ConditionalOnBean(type = "perry.demo.springbootdemo.config.MyService")
public class MyConfig {...}

运行main函数,报如下错误:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myConfig' available

说明MyService类虽然存在,但并没有实例化。所以需实例化才能正常运行。

新建InitComponent类,实例化MyService:

package perry.demo.springbootdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InitComponent {

    @Bean
    public MyService myService(){
        MyService myService = new MyService();
        myService.setName("test");
        return myService;
    }

}

再次运行程序,已不报错。

@ConditionalOnMissingBean

@ConditionalOnMissingBean@ConditionalOnMissingBean类似,当不存在MyService的Bean时,实例化MyConfig。

//当不存在MyService的Bean时,实例化MyConfig
@ConditionalOnMissingBean(type = "perry.demo.springbootdemo.config.MyService")
public class MyConfig {}

@ConditionalOnSingleCandidate

//当只存在一个MyService的Bean时,实例化MyConfig
@ConditionalOnSingleCandidate(type = "perry.demo.springbootdemo.config.MyService")
public class MyConfig {}

其他条件注解类似,这里不再列出。

其他注解相关代码如下:
MyConfig类:


import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.text.DateFormat;

@Configuration(proxyBeanMethods = false)
//仅仅当perry.demo.springbootdemo.config.MyService类存在时,该配置类生效
//@ConditionalOnClass(name = "perry.demo.springbootdemo.config.MyService")
//仅仅当perry.demo.springbootdemo.config.MyService类不存在时,该配置类生效
//@ConditionalOnMissingClass(value = "perry.demo.springbootdemo.config.MyService")
//当存在MyService的Bean时,实例化MyConfig
//@ConditionalOnBean(type = "perry.demo.springbootdemo.config.MyService")
//当只存在一个MyService的Bean时,实例化MyConfig
@ConditionalOnSingleCandidate(type = "perry.demo.springbootdemo.config.MyService")
//当不存在MyService的Bean时,实例化MyConfig
//@ConditionalOnMissingBean(type = "perry.demo.springbootdemo.config.MyService")

public class MyConfig {

    @Bean
    public MyBean myBean(){
        return new MyBean();
    }

//    @Bean
    //存在下述属性,才会实例化Bean
//    @ConditionalOnProperty(name = "mytest",prefix = "perry.demo",havingValue = "ball")
    //Spring MVC情况下生效
//    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//    @ConditionalOnWarDeployment
//    @ConditionalOnNotWarDeployment
    //下述表达式myService.name 代表 InitComponent 类中 myService bean的name属性
//    @ConditionalOnExpression("myService.name eq \"test1\"")
////    @ConditionalOnCloudPlatform()
////    @ConditionalOnJava()
//    @ConditionalOnJndi
//    public DateFormat dateFormat(){
//        return DateFormat.getDateInstance();
//    }
}

application.yml:

perry:
  demo:
    mytest: ball