本专栏将从基础开始,循序渐进,以实战为线索,逐步深入SpringBoot相关知识相关知识,打造完整的云原生学习步骤,提升工程化编码能力和思维能力,写出高质量代码。希望大家都能够从中有所收获,也请大家多多支持。
专栏地址:SpringBoot专栏
本文涉及的代码都已放在gitee上:gitee地址
如果文章知识点有错误的地方,请指正!大家一起学习,一起进步。

1 条件注解

条件注解可用于修饰@Configuration类或@Bean方法等,表示只有当特定条件有效时,被修饰的配置类或配置方法才会生效。正是得益于条件注解的帮助,Spring Boot 的自动配置才能执行类似于如下的智能行为。

➢ 当Spring Boot检测到类加载路径包含某个框架时,会自动配置该框架的基础Bean。

➢ 只有当开发者没配置某些Bean时,Spring Boot才会在容器中自动配置对应的Bean。

➢ 只有当开发者配置了某些属性时,Spring Boot才会在容器中自动配置对应的Bean。

总结起来,Spring Boot的条件注解可支持如下几类条件。

➢ 类条件注解:@ConditionalOnClass、@ConditionalOnMissingClass。 (注意,这里指的是项目中是否有某一个类,而不是容器中是否有该类)

➢ Bean条 注解:@Conditional On Missing Bean、@Conditional On Bean、@Conditional On Single Candidate、@Conditional On Missing FilterBean。

➢ 属性条件注解:@ConditionalOnProperty。

➢ 资源条件注解:@ConditionalOnResource。

➢ Web应用条件注解:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication、@ConditionalOnWarDeployment。

➢ SpEL表达式条件注解:@ConditionalOnExpression。

➢ 特殊条件注解:@ConditionalOnCloudPlatform、@ConditionalOnJava、@ConditionalOnJndi、@ConditionalOnRepositoryType。

上面这些条件注解都是基于Spring的@Conditional条件注解变化而来的。

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

1.1 @ConditionalOnClass注解

下面通过例子来示范@ConditionalOnClass注解的用法。首先创建一个Maven项目,定义如下配置类:

  • src/main/java/com/example/_003configtest/config/MyConfigTest.java
package com.example._003configtest.config;

import com.example._003configtest.mybean.MyBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// proxyBeanMethods = true  :单例模式,保证每个@Bean方法被调用多少次返回的组件都是同一个
// proxyBeanMethods = false :原型模式,每个@Bean方法被调用多少次返回的组件都是新创建的
@Configuration(proxyBeanMethods = false)
//仅当com.mysql.cj.jdbc.Driver类存在时该配置类生效
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public class MyConfigTest {
    @Bean
    public MyBean myBean(){
        return new MyBean();
    }
}

上面配置类使用了@ConditionalOnClass(name=“com.mysql.cj.jdbc.Driver”)修饰,这意味着只有当com.mysql.cj.jdbc.Driver类存在时,该配置类才会上面配置类使用了@ConditionalOnClass(name=“com.mysql.cj.jdbc.Driver”)修饰,这意味着只有当com.mysql.cj.jdbc.Driver类存在时,该配置类才会生效。此处使用的com.mysql.cj.jdbc.Driver类可改为任何要检查的类,这里仅仅是随便选一个目标类进行示范,并没有任何特别的意义。

该示例的主类代码如下:

package com.example._003configtest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
public class Application {

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

}

此时项目还没有导入mysql驱动,运行项目结果如下:

image-20220608193929410

maven中添加mysql依赖:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>

再次访问,结果如下:

image-20220608194032237

1.2 @ConditionalOnMissingClass注解

@ConditionalOnMissingClass注解的用法与@ConditionalOnClass注解的用法大致相同,只不过它要求的是被检查类不存在,这样被它修饰的配置类或方法才会生效。

1.3 @ConditionalOnMissingBean、@ConditionalOnBean、@ConditionalOnSingleCandidate注解

@ConditionalOnMissingBean、@ConditionalOnBean、@ConditionalOnSingleCandidate都用于要求目标Bean存在或不存在(带Missing的注解要求目标Bean不存在)。

如果@ConditionalOnBean、@ConditionalOnMissingBean 注解不指定任何属性,则默认根据目标Bean的类型进行检查,默认检查被修饰的方法所返回的Bean类型。例如如下代码片段:

image-20220608195313029

上面配置意味着当容器中不存在MyService类型的Bean时,该配置方法就会生效。

如果要检查具有特定ID的Bean是否存在,则需要指定name属性。例如如下代码片段:

image-20220608195347173

上面配置意味着只要容器中不存在ID为jdbcTemplate的Bean,该配置方法就会生效。

1.4 @ConditionalOnMissingFilterBean注解

@ConditionalOnMissingFilterBean注解相当于@ConditionalOnMissingBean的特殊版本,它专门用于检查容器中是否存在指定类型的javax.servlet.Filter,因此它只能通过value属性指定其要检查的Filter的类型。

1.5 @ConditionalOnProperty注解

@ConditionalOnProperty注解用于检查特定属性是否具有指定的属性值。该注解支持如下属性:

➢ String[] value:指定要检查的属性。

➢ String[] name:指定value属性的别名。

➢ String havingValue:指定被检查属性必须具有的属性值。

➢ String prefix:自动为各属性名添加该属性指定的前缀。

➢ boolean matchIfMissing:指定当属性未设置属性值时,是否通过检查。

例如:

@Configuration(proxyBeanMethods = true)
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public class MyConfigTest {
    @Bean
    @ConditionalOnProperty(name="test",havingValue = "foo",prefix = "com.example._003configtest")
    public MyBean myBean(){
        return new MyBean();
    }
}

配置application.properties:

# 应用名称
spring.application.name=003-config-test
# 应用服务 WEB 访问端口
server.port=8080

com.example._003configtest.test = foo

此时Bean容器中有MyBean的对象。

1.6 @ConditionalOnResource注解

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

1.7 @ConditionalOnWebApplication注解

@ConditionalOnWebApplication要求当前应用必须是Web应用时,其修饰的配置类或方法才会生效。使用该注解时可通过type属性指定Web应用的类型,该属性支持如下三个枚举值。

➢ ANY:当前应用是任何Web应用时,该注解修饰的配置类或方法都会生效。

➢ REACTIVE:只有当应用是反应式Web应用时(Spring WebFlux),该注解修饰的配置类或方法才会生效。

➢ SERVLET:只有当应用是基于Servlet的Web应用时(Spring MVC),该注解修饰的配置类或方法才会生效。

下面通过一个例子来示范@ConditionalOnWebApplication 注解的用法。

在配置类中添加如下代码:

@Configuration(proxyBeanMethods = true)
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public class MyConfigTest {
    @Bean
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
    public MyBean myBean(){
        return new MyBean();
    }
}

直接访问的结果如下:

image-20220608220325885

该异常说明@ConditionalOnWebApplication注解修饰的方法并未生效。上面的粗体字注解要求当前应用是反应式Web应用,只有这样该注解修饰的方法才会生效。

下面修改本例代码,改为Spring WebFlux的项目程序:

  • 添加webFlux依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>2.7.0</version>
</dependency>
  • 修改main
package com.example._003configtest;

import com.example._003configtest.mybean.MyBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@ImportResource("classpath:beans.xml")
@SpringBootApplication
public class Application {

    public static void main(String[] args) throws InterruptedException {
        SpringApplication app = new SpringApplication(Application.class);
        //设置web应用的类型,如果不设置则使用默认的类型,如果有Spring Web依赖,则自动是基于Servlet的Web应用
        //如果有Spring WebFlux依赖,则自动是反应式Web应用
        app.setWebApplicationType(WebApplicationType.REACTIVE);
        //创建Spring容器,运行Spring Boot应用
        ConfigurableApplicationContext run = app.run(args);
        System.out.println(run.getBean("myBean"));
    }

}

此时再次访问,结果如下:

image-20220608222032780

1.8 其他不常用条件注解

  1. @ConditionalOnNotWebApplication要求当前应用不是Web应用时,该注解修饰的配置类或方法才会生效。

  2. @ConditionalOnWarDeployment要求当前应用以传统WAR包方式被部署到Web服务器或应用服务器中时(不以独立Java程序的方式运行),该注解修饰的配置类或方法才会生效。

  3. @ConditionalOnNotWebApplication和**@ConditionalOnWarDeployment**用起来更简单,它们都不需要指定任何属性。

  4. @ConditionalOnExpression 注解要求指定SpEL表达式的值为true,这样其所修饰的配置类或方法才会生效。例如下面的配置类。

@Configuration(proxyBeanMethods = true)
//仅当com.mysql.cj.jdbc.Driver类存在时该配置类生效
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public class MyConfigTest {
    @Bean
    //当user.active表达式为true时,该方法才会生效,user是容器中的一个bean,active是该bean的一个属性
    @ConditionalOnExpression("user.active")
    public MyBean myBean(){
        return new MyBean();
    }
}
  1. @ConditionalOnCloudPlatform 注解要求应用被部署在特定云平台上,这样其修饰的配置类或方法才会生效。该注解可通过value属性指定它所要求的云平台,该value属性支持如下枚举值。

➢ CLOUD_FOUNDRY:要求应用被部署在CLOUD_FOUNDRY云平台上时,该注解修饰的配置类或方法才会生效。

➢ HEROKU:要求应用被部署在HEROKU平台上时,该注解修饰的配置类或方法才会生效。

➢ KUBERNETES:要求应用被部署在K8s平台上时,该注解修饰的配置类或方法才会生效。

➢ SAP:要求应用被部署在SAP云平台上时,该注解修饰的配置类或方法才会生效。

@ConditionalOnJava注解对目标平台的Java版本进行检测,它既可要求目标平台的Java版本是某个具体的版本,也可要求其高于或低于某个版本。使用该注解时可指定如下两个属性。

➢ JavaVersion value:指定要求的Java版本。

➢ ConditionalOnJava.Range range:该属性支持EQUAL_OR_NEWER(大于或等于value属性指定的版本)和 OLDER_THAN(小于 value 属性指定的版本)两个枚举值。如果不指定该属性,则要求目标平台的Java版本必须是value属性所指定的版本。

  1. 下面的配置类示范了**@ConditionalOnJava**注解的用法。

image-20220608222421087

上面粗体字注解要求目标平台的Java版本必须高于或等于11时,该配置才会生效。因此,只有用Java 11或更新版本的Java运行该程序,才会看到容器中的dateFormat Bean。

  1. @ConditionalOnJndi注解要求指定JNDI必须存在,使用该注解时通过 value属性指定要检查的JNDI。

  2. @ConditionalOnRepositoryType注解要求特定的Spring Data Repository被启用时,其修饰的配置类或方法才会生效。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐