【Spring杂烩】初探Spring的条件装配

2024-01-14 12:38

本文主要是介绍【Spring杂烩】初探Spring的条件装配,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

初尝Spring的条件装配


为了了解SpringBoot自动化装配的机制,所以必须先了解Spring的一些基础,Spring的条件装配就是其中之一

  • 前提概要
    • 条件装配知识脑图
    • 条件装配的定义
    • 条件装配的实现
    • 条件装配的作用
    • 条件装配的应用
  • 条件装配的源码体现
    • 注解模式
    • 编程模式
  • 自定义条件装配
    • 注解模式
    • 编程模式
  • 参考资料

前提概要


条件装配知识脑图
条件装配的定义
  • Bean装配的前置条件判断
  • 简而言之就是根据条件来判断这个Bean是否要被Spring容器装配
条件装配的实现

条件装配有两种实现方式:

  • 注解模式:@Profile
  • 编程模式:@Conditional
条件装配的作用

可以根据条件来选择是否需要在Spring容器中装配这个Bean,比如说:

  • 当Servlet依赖没有的时候,不装配WebMvc的各种Bean
  • 当某个Bean已经不存在的时候,就不重复装配
  • 根据不同的系统环境,装配不同的Bean
条件装配的应用
  • Spring Framework
    @Profile
  • Spring Boot
    @ConditionalOnMissingBean
    @ConditionalOnClass

条件装配的源码体现


注解模式

…这个没什么好说的,@Profile貌似使用的比较少,但是我们通常也知道一个SpringBoot多环境配置的特性,可以通过注解模式实现。但是在Spring 4.0开始,@Profile模式被修改过,实际的底层实现依然是编程模式

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {/*** The set of profiles for which the annotated component should be registered.*/String[] value();}

从源码中,我们可以看到,它使用了编程模式的@Conditional注解,并指定了一个Condition接口的实现类ProfileCondition


编程模式

我们就从@ConditionalOnMissingBean(是否存在某个Bean)说起。

第一步,在IDE中寻找了一下,使用了@ConditionalOnMissingBean的地方,我们就从spring boot的webmvc的配置类开始说起 WebMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}

我们看到 WebMvcAutoConfiguration配置类的定义,可以看到,类上是有@ConditionalOnMissingBean注解的声明,并且指定了一个类WebMvcConfigurationSupport,意思就是说当Spring容器中没有WebMvcConfigurationSupport这个Bean的时候,就会装配WebMvcAutoConfiguration 配置类

第二步,我们看check一下@ConditionalOnMissingBean注解的源码

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {Class<?>[] value() default {};String[] type() default {};Class<?>[] ignored() default {};String[] ignoredType() default {};Class<? extends Annotation>[] annotation() default {};String[] name() default {};SearchStrategy search() default SearchStrategy.ALL;Class<?>[] parameterizedContainer() default {};
}

嗯,成员属性还挺多的,但我们也注意到注解的声明中,存在@Conditional注解,其指定了一个Condition接口的实现类OnBeanCondition.

第三步,我们查看一下@Conditional注解指向的实现类OnBeanCondition. Oh~,它还蛮复杂的,所以我们只能简化一下,只看与Condition接口相关的方法,最后发现OnBeanCondition类,最终是通过继承SpringBootCondition去实现Condition方法的,为了方便理解,这里放了结构图:
在这里插入图片描述

第四步,寻找matchs()方法,发现是由SpringBootContidion实现的(SpringBootContidion就是SpringBoot对Condition的再封装

	@Overridepublic final boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {String classOrMethodName = getClassOrMethodName(metadata);try {//如何获取ConditionOutcome outcome = getMatchOutcome(context, metadata);logOutcome(classOrMethodName, outcome);recordEvaluation(context, classOrMethodName, outcome);//重点return outcome.isMatch();}catch (NoClassDefFoundError ex) {throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "+ ex.getMessage() + " not "+ "found. Make sure your own configuration does not rely on "+ "that class. This can also happen if you are "+ "@ComponentScanning a springframework package (e.g. if you "+ "put a @ComponentScan in the default package by mistake)",ex);}catch (RuntimeException ex) {throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);}}

以上是SpringBootCondition实现的matches方法,OnBeanCondition继承了下来。我们可以看到这个方法的返回值是通过outcome.isMatch(),而这个outcome又是通过getMatchOutcome()方法获取的,这个方法又是谁的呢?是SpringBootCondition抽象类的抽象方法,最终是OnBeanCondition实现的

第五步,查看OnBeanCondition的getMatchOutcome()方法,发现…emmm,更加复杂了。那就再简化简化

  • 总之这个方法会判断传入的metadate是不是ConditionalOnMissingBean注解类型,类似switch的功能,判断好久跳转到对应的逻辑块执行.
  • 然后调用一个getMatchingBeans()方法,获取Bean然后调用一个getMatchingBeans()方法,获取Bean
  • 根据返回的matchResult判断是否有对应的Bean,返回一个matchMessage结果
  • 最后这个matchMessage就会被SpringBootCondition获取到,用于给matches方法判断是true,还是false

第六步,当matchs方法返回了true 或者false之后,Spring容器就会根据这个flag判断是否装配WebMvcAutoConfiguration配置类

小结:

  • 的确,内部的逻辑比较复杂,所以暂时就忽略了很多部分,有一些地方我自己也没有弄懂,所以就不能太详细的说明,所以如有错误,请指出
  • 虽然OnBeanCondition非常的复杂,那是因为SpringBoot本身就是一个复杂的东西,所以我们自己实现的时候,未必需要实现的跟它一样复杂。

自定义条件装配


注解模式
目录结构,总共四个类

在这里插入图片描述
这里要实现一个计算服务,根据不同的JDK版本,执行不同的代码

  • CalculateService
    计算服务接口
  • Java7CalculateService
    Java 7 计算服务实现类,通过for循环实现
  • Java8CalculateService
    Java 8 计算服务实现类,通过lambda和Stream实现
  • Demo2Application
    SpringBoot启动引导类
CalculateService
public interface CalculateService {Integer sum(Integer...values);
}
Java7CalculateService
*** Java7计算服务* @author liwenjie*/
@Profile("Java7")
@Service
@Slf4j
public class Java7CalculateService implements CalculateService {@Overridepublic Integer sum(Integer... values) {int sum = 0;for (Integer i : values){sum = sum + i;}log.error("Java7 Calculating");return sum;}
}
**Java8CalculateService
/*** Java8计算服务* @author liwenjie*/
@Profile("Java8")
@Service
@Slf4j
public class Java8CalculateService implements CalculateService {@Overridepublic Integer sum(Integer... values) {log.error("Java8 Calculating");return Stream.of(values).reduce(0,Integer::sum);}
}
Demo2Application
@Slf4j
@SpringBootApplication
public class Demo2Application {//获取计算服务实现类@AutowiredCalculateService calculateService;//通过SpringApplication的方式修改profiles//当然你也可以在application.properties中配置spring.profiles.active=Java7public static void main(String[] args) {new SpringApplicationBuilder(Demo2Application.class).profiles("Java7").run(args);}@PostConstructpublic void init(){calculateService.sum(1,2,3,4,5);}}
  • 最后就会实际运行的就是Java7的实现类
  • 要注意的是,我这里使用了lombok插件(强烈推荐),所以没有的把log.error修改为System.out.println就好了


编程模式
目录结构,三个类
  • ConditionOnSystemProperty注解
    我们这里的作用就是判断系统的Property值是否跟注解的属性值相同,如果相同则装配该Bean
  • OnSystemPropertyCondition实现类
    具体的Condition实现类
  • Demo2Application类
    SpringBoot启动引导类
ConditionOnSystemProperty

模拟@ConditionalOnMissingBean注解

@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionOnSystemProperty {/***  系统名称,property的key* @return*/String name();/*** 系统属性值,property的value* @return*/String value();
}
OnSystemPropertyCondition
public class OnSystemPropertyCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {//获取ConditionOnSystemProperty的成员属性,最终map有两个元素,name和valueMap<String ,Object> attributes = metadata.getAnnotationAttributes(ConditionOnSystemProperty.class.getName());//获取属性名称String propertyName = String.valueOf(attributes.get("name"));//获取属性值String propertyValue = String.valueOf(attributes.get("value"));//从系统的Property中传入key,获取valueString systemPropertyValue = System.getProperty(propertyName);//最后比较propertyValue 和 systemPropertyValue 是否相等,并返回return systemPropertyValue.equals(propertyValue);}
}
Demo2Application

@SpringBootApplication
public class Demo2Application {@Bean@ConditionOnSystemProperty(name="user.name",value = "xxxx")public String Hello(){return "Hello World";}public static void main(String[] args) {new SpringApplicationBuilder(Demo2Application.class).run(args);}}
  • 结果就是,如果系统的property中没有key为user.name的元素,或者key为user.name的值不为xxxx,那么Spring容器就不会装配hello这个Bean
小结:
  • 自定义条件判断注解,注解内部需要有@Conditional,并指向一个Condition接口的实现类
  • Condition接口的实现类需要实现matchs方法
  • Spring会根据matchs方法返回flag来决定是否装配被自定义条件判断注解修饰的Bean

参考资料


  • SpringBoot2 | 条件注解 @ConditionalOnBean 原理源码分析(七)- 作者:张书康

这篇关于【Spring杂烩】初探Spring的条件装配的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/605191

相关文章

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.