前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >14-SpringBoot自动配置-Condition

14-SpringBoot自动配置-Condition

作者头像
Devops海洋的渔夫
发布2022-03-23 16:10:52
5390
发布2022-03-23 16:10:52
举报
文章被收录于专栏:Devops专栏

14-SpringBoot自动配置-Condition

前言

我们在前面的篇章基本上已经熟悉了怎么使用SpringBoot,但是呢。对于SpringBoot的一些高级的特性 或者 底层原理还是不是很清楚。

例如SpringBoot是如何根据启动坐标去创建需要的 bean。

有时候我们可能需要通过一些条件,再去创建 bean。对于这种场景,我们该怎么做呢?

这个时候我们就要介绍以下 Condition 的使用了。

SpringBoot自动配置-Condition

Condition是Spring4.0后引入的条件化配置接口,通过实现Condition接口可以完成有条件的加载相应的Bean

@Conditional要配和Condition的实现类(ClassCondition)进行使用

案例1 - 通过读取启动坐标的配置,判断是否创建 bean 对象

需求

在Spring的 IOC 容器中创建一个User的bean,现要求:

  • 导入 Jedis 坐标后,加载该 Bean,没导入,则不加载。

步骤

1.搭建演示项目

初始化的pom.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- springboot父工程   -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!-- 项目坐标   -->
    <groupId>com.lijw</groupId>
    <artifactId>springboot-condition</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>springboot-condition</name>

    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <!-- 项目依赖   -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

好了,现在我们搭建好了项目,也配置了 redis 的启动依赖,我们下面来逐步实验一下。

2. 获取 SpringBoot 的 IOC 容器

首先我们要知道 SpringBoot IOC 容器怎么获取,如下:

代码语言:javascript
复制
package com.lijw.springbootcondition;

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

import javax.lang.model.element.VariableElement;

@SpringBootApplication
public class SpringbootConditionApplication {

    public static void main(String[] args) {
        // 启动SpringBoot应用就可以获取 IOC 容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
        // 通过IOC容器获取Bean
        Object redisTemplate = context.getBean("redisTemplate");
        System.out.println(redisTemplate);
    }

}

在上面,我们通过SpringBoot的应用启动获取到了 IOC 容器,并通过 IOC 容器获取到了 redisTemplate 的 Bean。那么这个 Bean 能否控制产生或者不产生的呢?

这时候我们就要来介绍 Condition 接口,通过这个接口 我们将可以控制一个 Bean 是否能够自动生成。

我们来回顾一下需求:

“在Spring的 IOC 容器中创建一个User的bean,现要求:

  • 导入 Jedis 坐标后,加载该 Bean,没导入,则不加载。

下面我们首先来写一个 User 类,然后再写要给 UserConfig 类来控制 User 类是否创建 Bean

3. 创建 User 类 以及 尝试获取 Bean
代码语言:javascript
复制
package com.lijw.springbootcondition.domain;

public class User {
}

尝试获取 Bean 如下:

代码语言:javascript
复制
package com.lijw.springbootcondition;

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

import javax.lang.model.element.VariableElement;

@SpringBootApplication
public class SpringbootConditionApplication {

    public static void main(String[] args) {
        // 启动SpringBoot应用就可以获取 IOC 容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
        // 通过IOC容器获取Bean
        Object user = context.getBean("user");
        System.out.println(user);
    }

}

可以发现 User 类目前还不能获取 Bean,因为我们没有将 User 类定义为组件,所以需要写一个获取 User 类对象的 方法。

4.创建 UserConfig 的配置文件,用来加载配置 创建 User 类的 bean
代码语言:javascript
复制
package com.lijw.springbootcondition.config;

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

@Configuration
public class UserConfig {

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

此时有了这个返回 @Bean 的方法,我们应该就可以获取 user 的 bean 对象了,如下:

好了,既然已经可以获取到 user 类的 bean,那么我们就可以在这个 bean 方法上来控制是否生成 bean 对象。

5. 使用 @Conditional 以及 实现 Condition 接口来控制是否生成 bean 对象
5.1 使用 @Conditional
代码语言:javascript
复制
package com.lijw.springbootcondition.config;

import com.lijw.springbootcondition.condition.ClassCondition;
import com.lijw.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
    @Conditional(ClassCondition.class) // 定义ClassCondition设置创建bean的条件规则
    public User user(){
        return new User();
    }
}
5.2 编写 ClassCondition 实现 Condition 接口,定义是否创建 bean
代码语言:javascript
复制
package com.lijw.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 {

    /**
     *
     * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
     * @param metadata 注解元对象。 可以用于获取注解定义的属性值
     * @return Boolean: false不创建bean, true创建bean
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

此时,ClassCondition 重写的 matches 方法默认返回为 false,所以我们下面使用 IOC 获取 user bean 对象会失败,如下:

如果设置为 true,那么则可以获取到 bean 对象:

那么到这里,我们基本上就可以知道大概如何实现控制是否生成bean的需求了。

5.2 编写 ClassCondition 判断是否引入了 redis依赖,如果引入则允许创建 user,反之不允许
代码语言:javascript
复制
package com.lijw.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 {

    /**
     *
     * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
     * @param metadata 注解元对象。 可以用于获取注解定义的属性值
     * @return Boolean: false不创建bean, true创建bean
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Boolean flag = true;

        try {
            // 获取redisTemplate的依赖文件是否存在
            Class<?> cls = Class.forName("org.springframework.data.redis.core.RedisTemplate");
        } catch (ClassNotFoundException e) {
            // 如果异常不存在,则设置为false,不允许创建bean
            flag = false;
        }

        return flag;
    }
}

此时,如果我们注释了redis依赖,来看看是否能创建 user 的 bean,如下:

获取 bean 失败了,说明我们的方法是正确的。

下面我们再配置依赖,看看能否获取成功,如下:

成功获取 bean 了。

6.小结

在上面我们已经通过一个类依赖是否存在,来判断是否创建 bean 对象。但是我们这个类依赖 目前是直接 硬编码写死在代码里头的!

这样肯定不灵活,如果我们想要改其他类,就要每次到代码里面去修改,能不能写到一个注解上,然后我们在注解上配置需要依赖的类呢?

下面我们来看看怎么做。

案例2 - 通过动态指定启动坐标的配置,判断是否创建 bean 对象

需求

在Spring的 IOC 容器中创建一个User的bean,现要求:

  • 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。

步骤

1.创建自定义条件注解类

创建 @ConditionOnClass 继承 @Conditional,可以自定义属性

代码语言:javascript
复制
package com.lijw.springbootcondition.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class) // 继承 @Conditional 条件注解
public @interface ConditionOnClass {
    String[] value(); // 定义数组,方便后续可以写入多个依赖类
}
2.在创建 Bean 方法换上使用自定义的条件注解类,并指定希望设置的依赖类
代码语言:javascript
复制
@Bean
//@Conditional(ClassCondition.class) // 定义ClassCondition设置创建bean的条件规则
@ConditionOnClass("org.springframework.data.redis.core.RedisTemplate")
public User user(){
    return new User();
}

在这里我们已经可以将依赖类作为参数写到注解中,那么注解如何将参数传递到条件处理类中呢?

3.认识 ClassCondition 类下 matches(ConditionContext context, AnnotatedTypeMetadata metadata) 的两个参数
代码语言:javascript
复制
public class ClassCondition implements Condition {

    /**
     *
     * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
     * @param metadata 注解元对象。 可以用于获取注解定义的属性值
     * @return Boolean: false不创建bean, true创建bean
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)

也就是我们可以通过 metadata 这个参数获取注解设置的参数。

4.通过 metadata 获取注解类的参数
代码语言:javascript
复制
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // metadata参数使用getAllAnnotationAttributes方法,传入注解的名称,将可以获取注解的属性值
    Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
    System.out.println(map);

可以确认已经可以获取 value 属性的 Sting数组了。

下面我们继续来遍历一下 value 属性的字符串数组:

代码语言:javascript
复制
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // metadata参数使用getAllAnnotationAttributes方法,传入注解的名称,将可以获取注解的属性值
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
        // 获取value属性的String[],并且遍历
        String[] value = (String[]) map.get("value");
        for (String clazzName : value) {
            System.out.println(clazzName);
        }
5.遍历校验 metadata 获取注解类的参数
代码语言:javascript
复制
package com.lijw.springbootcondition.condition;


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

import java.util.List;
import java.util.Map;

public class ClassCondition implements Condition {

    /**
     *
     * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
     * @param metadata 注解元对象。 可以用于获取注解定义的属性值
     * @return Boolean: false不创建bean, true创建bean
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // metadata参数使用getAllAnnotationAttributes方法,传入注解的名称,将可以获取注解的属性值
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());

        // 设置返回的flag
        Boolean flag = true;

        try {
            // 获取value属性的String[],并且遍历
            String[] value = (String[]) map.get("value");
            for (String clazzName : value) {
                System.out.println(clazzName);
                // 尝试获取依赖文件,根据异常判断是否存在
                Class<?> cls = Class.forName(clazzName);
            }
        } catch (ClassNotFoundException e) {
            // 如果异常不存在,则设置为false,不允许创建bean
            flag = false;
        }

        return flag;
    }
}
6.测试验证

首先当然是验证设置了依赖的情况,如下:

可以看到成功获取 bean

下面我们来注释依赖,确认异常的情况:

成功了,当依赖不存在,则无法获取 bean

而且目前我们的依赖可以动态在注解类上填写。

SpringBoot 源码中提供的 Condition 条件注解类

在上面我们虽然自己写了一个条件注解类,但是 SpringBoot 其实已经提供了大量的条件注解类了,我们查看源码如下:

可以看到源码其实也是这些写的。

SpringBoot 提供的常用条件注解:

ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean

ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean

ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean

其中源码之后 ConditionalOnProperty 这个注解我们可能经常会用到,通过配置文件的属性来判断是否创建 Bean

下面我们来演示一下。

演示使用 ConditionalOnProperty 注解

1.编写创建 user bean 的方法,需要设置 ConditionalOnProperty 定义需要的属性值

image-20220224204200775

代码语言:javascript
复制
package com.lijw.springbootcondition.config;

import com.lijw.springbootcondition.condition.ClassCondition;
import com.lijw.springbootcondition.condition.ConditionOnClass;
import com.lijw.springbootcondition.domain.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {

    @Bean
    //@Conditional(ClassCondition.class) // 定义ClassCondition设置创建bean的条件规则
    @ConditionOnClass("org.springframework.data.redis.core.RedisTemplate")
    public User user(){
        return new User();
    }

    @Bean
    @ConditionalOnProperty(name = "test", havingValue = "condition") // 配置文件存在 test=condition 则创建bean对象
    public User user2(){
        return new User();
    }
}
2.编写获取 user2 的 bean 对象
代码语言:javascript
复制
package com.lijw.springbootcondition;

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

import javax.lang.model.element.VariableElement;

@SpringBootApplication
public class SpringbootConditionApplication {

    public static void main(String[] args) {
        // 启动SpringBoot应用就可以获取 IOC 容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
        // 通过IOC容器获取Bean
        Object user = context.getBean("user2");
        System.out.println(user);

    }

}
3.首先没有配置属性值,验证是否能创建 user2 的 bean 对象

此时没有自动创建 user2 的 bean 对象。

4.配置需要的属性值,验证是否能创建 user2 的 bean 对象

在配置文件设置了属性值之后,成功自动创建 user2 的 bean

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 海洋的渔夫 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 14-SpringBoot自动配置-Condition
    • 前言
      • SpringBoot自动配置-Condition
        • 案例1 - 通过读取启动坐标的配置,判断是否创建 bean 对象
          • 需求
          • 步骤
        • 案例2 - 通过动态指定启动坐标的配置,判断是否创建 bean 对象
          • 需求
          • 步骤
        • SpringBoot 源码中提供的 Condition 条件注解类
          • 演示使用 ConditionalOnProperty 注解
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档