文章目录

  • 前言
  • 简介
  • 创建 springboot-condition 模块
  • 开始操作
  • 获取容器中的 Bean
  • @Conditional() 注解
  • matches() 条件判断
  • 导入 Jedis 坐标后
  • 导入通过注解属性值 value 指定坐标
  • 思考
  • spring-boot-autoconfigure
  • condition 包
  • ConditionalOnClass
  • ConditionalOnBean
  • ConditionalOnProperty
  • ConditionalOnMissingBean
  • data 包
  • 演示
  • 总结
  • 自定义条件
  • SpringBoot 提供的常用条件注解

前言

SpringBoot 的自动配置分为 Condition、切换内置web服务器、Enable 注解、Import 注解、EnableAutoConfiguration 注解。
本章分享的是 Condition 的内容。

简介

Condition 是在 Spring 4.0 增加的条件判断功能,通过这个功能可以实现选择性的创建 Bean 操作。

创建 springboot-condition 模块

使用 IDEA 快速创建一个 SpringBoot 模块,不引入任何依赖。

springboot开启 compression_spring boot

开始操作

进入 SpringBoot 启动类,点击进入 run() 可以看到这个方法是有返回值的,返回值为 ConfigurableApplicationContext,这个返回值就是 IOC 容器。

springboot开启 compression_redis_02


springboot开启 compression_spring boot_03


获取 run() 的返回值。

// 启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);

获取容器中的 Bean

在 pom.xml 中导入 Redis 的起步依赖。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

在启动类中编写获取 redisTemplate 代码。

// 获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);

打印输出获取到的 redisTemplate。

springboot开启 compression_User_04


接下来创建 domain -> User 类

package com.xh.springbootcondition.domain;

public class User {
}

创建config -> UserConfig 类,类中添加返回值为 User 的方法,方法顶部添加 @Bean。

package com.xh.springbootcondition.config;

import com.xh.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {
    @Bean
    public User user(){
        return new User();
    }
}

在启动类中获取容器中的 user 对象并打印输出。

// 获取Bean,user
Object user = context.getBean("user");
System.out.println(user);

springboot开启 compression_User_05

@Conditional() 注解

Spring 在创建 Bean 时,我们可以在 UserConfig 中的 user 方法上添加条件注解 @Conditional()。

springboot开启 compression_User_06


@Conditional() 注解点进去可以看到这个注解需要一个 Class(因为是一个数组,所以可以导很多 Class ),这些 Class 必须都是 Condition 或者 Condition 的子类。

springboot开启 compression_java_07


而 Condition 就是核心的条件接口,点击进入 Condition 可以看到接口中只有一个 matches() 方法,返回值为 boolean 类型。

springboot开启 compression_redis_08


所以我们要使用 @Conditional() 注解就需要在注解中传入一个 Condition 条件接口的实现类,并且实现类要复写matches() ,返回 true 或者 false。

如果返回的是 true,那么 User 对应的 Bean 将会被 Spring 容器创建,如果返回的是 false,那么容器则不会创建 User 对应的 Bean 。

创建 condition -> ClassCondition 类,实现 Condition 接口,复写 matches() 。

package com.xh.springbootcondition.condition;

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

public class ClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

在 user 方法顶部的 @Conditional() 注解中放入创建好的 ClassCondition.class

@Conditional(ClassCondition.class)

由于我们的 ClassCondition 实现类中复写的 matches() 返回的是 false,那么 Spring 容器就不会创建 User 所对应的 Bean,测试运行输出会提示找不到 user。

springboot开启 compression_User_09

如果把返回条件改为 true,那么 User 对应的 Bean 就会被创建。

matches() 条件判断

我们可以在 复写的 matches() 中加入条件判断:

导入 Jedis 坐标后

在 pom.xml 中导入 Jedis 坐标

<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

判断是否导入 Jedis 坐标(仅用于测试用例,不推荐用来实际操作)

// 1.需求:导入 Jedis 坐标后,创建 Bean
        // 思路:判断 redis.clients.jedis.Jedis.class 是否存在
        boolean flag = true;
        try {
            Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
        } catch (ClassNotFoundException e) {
            flag = false;
            e.printStackTrace();
        }
        return flag;

运行启动类,成功输出 user。

springboot开启 compression_java_10


如果把 pom.xml 文件中的 Jedis 坐标注释掉,那么Spring 容器则不会创建 User 对应的 Bean,运行启动类就获取不到 user 对象。

导入通过注解属性值 value 指定坐标

上一种方法操作空间有限,接下来我们去自定义一个注解来实现动态导入指定值判断是否创建Bean。
创建 condition -> ConditionOnClass 定义一个注解,添加一些相对应的注解并定义 value 属性。

package com.xh.springbootcondition.condition;

import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;

/**
 * @Target 表示当前注解可以加到哪个范围上
 * ElementType.TYPE 类上
 * ElementType.METHOD 方法上
 * --------------------------------------
 * @Retention 表示注解生效的时机,为 RUNRIME
 * --------------------------------------
 * @Documented 表示生成 javadoc 的文档
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {
    String[] value();
}

修改 UserConfig 类中 user(),使用自定义的注解。

package com.xh.springbootcondition.config;

import com.xh.springbootcondition.condition.ConditionOnClass;
import com.xh.springbootcondition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {

    @Bean
//    @Conditional(ClassCondition.class)
    @ConditionOnClass("redis.clients.jedis.Jedis")
    public User user(){
        return new User();
    }
}

修改 ClassCondition 中 matches() 实现动态获取注解属性值。
修改前我们需要了解 matches() 中的两个参数:
ConditionContext context 和 AnnotatedTypeMetadata metadata。

package com.xh.springbootcondition.condition;

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

public class ClassCondition implements Condition {
    /**
     * 参数说明
     * @param context 上下文对象。用于获取环境,IOC 容器,ClassLoader 对象
     * @param metadata 注解元对象。可以获取注解定义的属性值。
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        /*
        // 1.需求:导入 Jedis 坐标后,创建 Bean
        // 思路:判断 redis.clients.jedis.Jedis.class 是否存在
        boolean flag = true;
        try {
            Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
        } catch (ClassNotFoundException e) {
            flag = false;
            e.printStackTrace();
        }
        return flag;
        */

        // 2.需求:导入通过注解属性值 value 指定坐标后创建 Bean
        // 获取注解属性值 value[]
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
        // System.out.println(map);
        String[] value = (String[]) map.get("value");
        boolean flag = true;
        try {
            for (String className : value) {
                Class<?> cls = Class.forName(className);
            }
        } catch (ClassNotFoundException e) {
            flag = false;
            e.printStackTrace();
        }
        return flag;
    }
}

运行启动类,输入获取的 user。

springboot开启 compression_redis_11


可以测试一个其他的坐标看看能不能创建出 User。

在 pom.xml 中添加 fastjson 坐标

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.4</version>
        </dependency>

修改 userConfig,将之前编写的 user 方法注释掉,重新编写一个方法,@ConditionOnClass 的 value 值放入 com.alibaba.fastjson.JSON 路径。

@Configuration
public class UserConfig {

//    @Bean
    @Conditional(ClassCondition.class)
//    @ConditionOnClass("redis.clients.jedis.Jedis")
//    public User user(){
//        return new User();
//    }
    @Bean
    @ConditionOnClass("com.alibaba.fastjson.JSON")
    public User user(){
        return new User();
    }
}

运行启动类,正确输出:

springboot开启 compression_spring_12

思考

读到这里的朋友可以回过头思考一下:根据我们之前在启动类中编写的获取 redisTemplate 的代码:

// 启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
// 获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");

SpringBoot 是如何知道要创建 RedisTemplate 的?
其实 SpringBoot 就是使用 Condition 去判断我们当前环境中有没有导入 Redis 的 start,如果导入了,那么就会创建 RedisTemplate。

spring-boot-autoconfigure

在 SpringBoot 中提供了非常多的 ConditionOn* 的类,我们可以在 External Libraries 中找到 spring-boot-autoconfigure 这个 SpringBoot 的自动配置 jar 包,其中有一个 condition 包,可以看到这个包里有很多 ConditionOn*。

springboot开启 compression_spring_13

condition 包
ConditionalOnClass

进入 ConditionalOnClass 中可以看到,他里面定义了一个注解,注解上面有一个 @Conditional,value 值为 OnClassCondition.class,这个操作就是在判断是否有对应的字节码文件。

springboot开启 compression_User_14


我们可以再看一看其他的,比如:

ConditionalOnBean

这个注解代表了:当你的环境中有 OnBeanCondition 这个 Bean 的时候,才会去创建 ConditionalOnBean 这个注解修饰的 Bean。

springboot开启 compression_redis_15

ConditionalOnProperty

当你的配置文件中配置了指定的属性,才会创建某一个 Bean。

springboot开启 compression_redis_16

ConditionalOnMissingBean

当你的 IOC 容器中没有指定 Bean 的时候,才会去初始化指定的 Bean。

springboot开启 compression_spring_17

data 包

data 包中有一个 redis 的包,redis 有一个配置类为 RedisAutoConfiguration,如图可以看到这个配置类在初始化 RedisTemplate,并且有一些初始化的条件:

@ConditionalOnClass(RedisOperations.class)
在当前环境中需要有 RedisOperations 字节码存在,才能去创建下面方法的 Bean。(因为这个注解是加在类上的,所以下面所有的方法都要满足这个条件才可以创建 Bean)
@ConditionalOnMissingBean(name = “redisTemplate”)
可以看到在 redisTemplate 方法上添加了一个这样的注解,他代表如果当前环境中不存在 redisTemplate,才会创建 RedisTemplate,如果存在,那就证明我们自己定义了 RedisTemplate,则不会创建。

springboot开启 compression_redis_18

演示

修改 UserConfig,添加一个 user2 方法,并且在方法上添加 @Bean 和 @ConditionalOnProperty(name = “csdn”, havingValue = “xinghe”) 注解。
上面讲到 @ConditionalOnProperty() 是当配置文件中某一个属性和对应的值存在,才会去加载 Bean,这个注解表示:当我们的配置文件中有一个键为 ”csdn“,值为 ”xinghe“ 的时候,才会去加载对应的 Bean。

@Configuration
public class UserConfig {

//    @Bean
    @Conditional(ClassCondition.class)
//    @ConditionOnClass("redis.clients.jedis.Jedis")
//    public User user(){
//        return new User();
//    }
    @Bean
    @ConditionOnClass("com.alibaba.fastjson.JSON")
    public User user(){
        return new User();
    }

    @Bean
    @ConditionalOnProperty(name = "csdn", havingValue = "xinghe")
    public User user2(){
        return new User();
    }
}

在启动类中添加获取 user2 的代码。

@SpringBootApplication
public class SpringbootConditionApplication {

    public static void main(String[] args) {
        // 启动SpringBoot的应用,返回Spring的IOC容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);

        // 获取Bean,redisTemplate
        Object redisTemplate = context.getBean("redisTemplate");
        System.out.println(redisTemplate);

        // 获取Bean,user
        Object user = context.getBean("user");
        System.out.println(user);

        // 获取Bean,user2
        Object user2 = context.getBean("user2");
        System.out.println(user2);
    }
}

因为我们没有在启动类中配置相应的属性,所以运行输出没有找到 user2。

springboot开启 compression_User_19

接下来我们在 application.properties 中配置相应的属性:

csdn=xinghe

再次运行,成功输出:

springboot开启 compression_java_20

总结

本章介绍了自定义的条件以及 SpringBoot 提供的常用条件注解。

自定义条件

  • 定义条件类:自定义类实现 Condition 接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean 值。
    matches 方法两个参数:
  1. context:上下文对象,可以获取属性值、类加载器、BeanFactory 等。
  2. metadata:元数据对象,用于获取注解属性。
  • 判断条件:在初始化 Bean 时,使用 @Conditional(条件类.class) 注解。

SpringBoot 提供的常用条件注解

  • ConditionalOnProperty:判断配置文件是否有对应的属性和值才初始化 Bean。
  • ConditionOnClass:判断环境中是否有对应的字节码文件才初始化 Bean。
  • ConditionOnMissingBean:判断环境中没有对应 Bean 才初始化 Bean。