强大!基于Spring Boot自定义条件动态注册组件

开发 开发工具
当Spring Boot预设的条件注解不能满足我们的需求时,我们还可以通过自定义的方式来实现更为复杂和特定的条件逻辑。 接下来,我们将详细介绍有关自定义条件注解的多方面知识的应用。

环境:SpringBoot3.4.0

1. 简介

@Conditional是Spring 4.0版本引入的一个强大注解,它允许开发者根据特定条件来决定是否创建某个Bean。该注解只有当满足预设条件时,Spring容器才会将对应的Bean实例化并加入到应用程序的上下文中。

通过@Conditional注解,开发者可以根据不同的环境(如开发、测试、生产环境)、配置文件中的属性、类路径中的特定类是否存在,或者任何自定义的逻辑条件,来动态地控制Bean的创建过程。这种机制极大地提高了Spring应用程序的可配置性和灵活性,使得开发者能够根据不同的场景和需求,智能地选择性地装配Bean。

@Conditional注解通常与@Configuration和@Bean注解一起使用,以标记那些需要条件化创建的Bean。在Spring Boot中,@Conditional注解更是被广泛应用,衍生出了如@ConditionalOnProperty、@ConditionalOnBean、@ConditionalOnClass等多个便捷的条件注解,进一步简化了条件配置的过程。

当Spring Boot预设的条件注解不能满足我们的需求时,我们还可以通过自定义的方式来实现更为复杂和特定的条件逻辑。

接下来,我们将详细介绍有关自定义条件注解的多方面知识的应用。

图片图片

2. 实战案例

2.1 自定义Condition接口

这里我们简单的根据配置文件中的属性来决定是否注册Bean对象。

public class EnvCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return "true".equals(env.getProperty("pack.api.enabled"));
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

只有配置文件中的"pack.api.enabled"设置为true才会对相应的Bean进行注册;自定义注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiCondition.class)
public @interface ConditionalOnApi {
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

接下来,进行测试:

@RestController
@RequestMapping("/api")
@ConditionalOnApi
public class ApiController {


  @PostConstruct
  public void init() {
    System.err.println("ApiController init...") ;
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

首先,配置文件不对pack.api.enabled进行配置,如果启动容器后没有输出任何内容则表示我们的条件注解生效了。

图片图片

没有任何的输出:

配置文件中设置pack.api.enabled=true, 再次启动服务

图片

成功注册bean。

2.2 带参数增强实现

在上面的示例中,我们直接在代码中硬编码了要判断的属性,这种做法缺乏灵活性。因此,在该示例中,我们将采用自定义注解属性来实现条件判断,以增强代码的灵活性和可配置性。

修改注解如下:

public @interface ConditionalOnApi {
  
  String value() ;
}
  • 1.
  • 2.
  • 3.
  • 4.

修改条件类:

public class ApiCondition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Map<String, Object> attrs = metadata.getAnnotationAttributes(
        ConditionalOnApi.class.getName()) ;
    String key = (String) attrs.get("value") ;
    Environment env = context.getEnvironment() ;
    return "true".equals(env.getProperty(key)) ;
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

最后,修改使用处。

@ConditionalOnApi("pack.api.enabled")
public class ApiController {}
  • 1.
  • 2.

通过上面的改造我们的自定义条件灵活多了。

2.3 基于SpringBootCondition实现

通过继承SpringBootCondition实现条件是官方推荐的方式。它的强大之处在于它提供了有用的日志记录功能,以帮助用户诊断哪些类被加载了。

自定义条件:

public class ApiMonitorCondition extends SpringBootCondition {
  private static final ConditionMessage.Builder message = ConditionMessage.forCondition("API Monitor");


  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    String monitorEnabled = context.getEnvironment().getProperty("pack.api.monitor.enabled");
    if ("true".equals(monitorEnabled)) {
      return ConditionOutcome.match(message.available("开启API监控功能"));
    }
    return ConditionOutcome.noMatch(message.because("API监控功能关闭"));
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

自定义注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ApiMonitorCondition.class)
public @interface ConditionalOnApiMonitor {
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

使用条件注解:

@Configuration
@ConditionalOnApiMonitor
public class ApiMonitorConfig {
}
  • 1.
  • 2.
  • 3.
  • 4.

接下来,进行测试。

首先,不配置pack.api.monitor.enabled或者设置为false,同时将debug设置为true,启动服务控制台输出如下:

图片图片

最后,将pack.api.monitor.enabled设置为true,控制台输出如下:

图片图片

2.4 多条件组合实现

在该案例中我们将采用多个条件来进行判断是否符合条件。我们还是使用上面的示例,条件的判断我们不仅判断属性,而且还会判断相应的类及Bean是否存在。

这里我们直接修改上面的条件注解:

public class ApiMonitorCondition extends SpringBootCondition {
  private static final ConditionMessage.Builder message = ConditionMessage.forCondition("API Monitor");
  private static final String CLASS_NAME = "com.pack.condition.test.MonitorComponent" ;


  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    String monitorEnabled = context.getEnvironment().getProperty("pack.api.monitor.enabled");
    boolean enabled = "true".equals(monitorEnabled) ;
    // 判断对应的类是否存在
    boolean isPresent = isPresent(CLASS_NAME, context.getClassLoader()) ;
    if (enabled) {
      if (isPresent) {
        try {
          // 判断对应的bean是否存在
          ConfigurableListableBeanFactory beanFactory = context.getBeanFactory() ;
          if (beanFactory.containsBean("monitorComponent")) { 
            return ConditionOutcome.match(message.available("开启API监控功能"));
          }
          return ConditionOutcome.noMatch(message.because("容器不存在beanName=monitorComponent的Bean对象"));
        } catch (Exception e) {
          return ConditionOutcome.noMatch(message.because("容器不存在【" + CLASS_NAME + "】类型的Bean"));
        }
      } else {
        return ConditionOutcome.match(message.because("API监控未能开启缺少【" + CLASS_NAME + "】类"));
      }
    } else {
      return ConditionOutcome.noMatch(message.because("API监控功能关闭"));
    }
  }
  
  private static boolean isPresent(String className, ClassLoader classLoader) {
    try {
      resolve(className, classLoader);
      return true;
    }
    return false ;
  }
  private static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
    if (classLoader != null) {
      return Class.forName(className, false, classLoader);
    }
    return Class.forName(className);
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

以上我们就完成了多个条件组件判断。

测试结果:

图片图片

存在对应的类及bean时输出如下:

图片图片

最终是匹配的。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2024-10-14 17:18:27

2022-04-24 15:17:56

鸿蒙操作系统

2022-06-27 08:16:34

JSON格式序列化

2021-11-01 10:21:36

鸿蒙HarmonyOS应用

2022-09-16 15:34:32

CanvasArkUI

2021-07-13 14:05:37

单点登录页面

2022-07-06 20:24:08

ArkUI计时组件

2009-06-25 14:53:35

自定义UI组件JSF框架

2023-01-03 07:40:27

自定义滑块组件

2022-03-01 16:09:06

OpenHarmon鸿蒙单选组件

2022-06-30 14:02:07

鸿蒙开发消息弹窗组件

2021-09-15 10:19:15

鸿蒙HarmonyOS应用

2022-07-15 16:45:35

slider滑块组件鸿蒙

2022-12-07 08:56:27

SpringMVC核心组件

2009-06-24 15:13:36

自定义JSF组件

2023-02-20 15:20:43

启动页组件鸿蒙

2018-06-21 14:46:03

Spring Boot异步调用

2021-11-22 10:00:33

鸿蒙HarmonyOS应用

2022-05-26 14:50:15

ArkUITS扩展

2022-02-16 16:09:12

鸿蒙游戏操作系统
点赞
收藏

51CTO技术栈公众号