Spring Framework 条件装配 之 @Conditional
Spring Framework 条件装配 之 @Conditional
前言
了解SpringBoot的小伙伴对Conditional注解一定不会陌生,在SpringBoot项目中,Conditional注解被广泛的使用以及扩展出了许多Condition派生注解。虽然Conditional在SpringBoot中被丰富了很多,但它是在Spring Framework 4.0中提出的,所以本文还是以Spring Framework 为基础进行讲解。
推荐阅读
黑色的眼睛 の 个人博客
Spring Framework 组件注册 之 FactoryBean
Spring Framework 组件注册 之 @Import
Spring Framework 组件注册 之 @Component
@Conditional 说明
要使用@Conditional
注解,必须先了解一下Conditiona
接口,它与@Conditional
注解配合使用,通过源码我们也可以看出,使用@Conditional
注解必须要指定实现Conditiona
接口的class。
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
在Conditiona
接口中,只定义了一个方法matches
,spring在注册组件时,也正是根据此方法的返回值TRUE/FALSE
来决定是否将组件注册到spring容器中
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context 条件判断的上下文环境
* @param metadata 正在检查的类或方法的注解元数据
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
在matches
中我们可以获取到ConditionContext
接口,根据此接口对象可以获取BeanDefinitionRegistry
,ConfigurableListableBeanFactory
等重要对象信息,根据这些对象就可以获取和检查spring容器初始化时所包含的所有信息,再结合业务需求,就可以实现组件注册时的自定义条件判断。
@Conditional 使用
首先定义两个普通的JavaBean类
@Data
public class Test {
private String id = "@Bean";
}
@Data
public class Test2 {
private String id = "@Conditional";
}
通过配置类和@Bean
注解,向spring容器中注册组件
/**
* spring组件配置类
*/
@Configuration
public class TestConfiguration {
/**
* 向spring容器中注册Test 类型下beanName为test的组件
*/
@Bean
public Test test() {
return new Test();
}
/**
* 根据TestCondition接口的条件判断向spring容器中注册Test2组件
*/
@Bean
@Conditional(TestCondition.class)
public Test2 test2() {
return new Test2();
}
}
自定义实现Condition
接口
public class TestCondition implements Condition {
/**
* 当IOC容器中包含 Test类的bean定义信息时,条件成立
*
* @param context 条件判断的上下文环境
* @param metadata 正在检查的类或方法的元数据
* @return 条件是否成立
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
return ArrayUtils.isNotEmpty(testBeanNames);
}
}
添加spring容器启动引导类
/**
* spring 容器启动引导类,测试 @Conditional 功能
*/
@ComponentScan("com.spring.study.ioc.condition")
public class TestConditionalBootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(TestConditionalBootstrap.class);
String[] names = applicationContext.getBeanNamesForType(Test.class);
System.out.println("---Test bean names : " + Arrays.asList(names));
names = applicationContext.getBeanNamesForType(Test2.class);
System.out.println("---Test2 bean names : " + Arrays.asList(names));
applicationContext.close();
}
}
运行spring引导类,控制台打印结果:
---hasTestBean : true
---Test bean names : [test]
---Test2 bean names : [test2]
由结果可以看出,Test正常注册到了spring容器中,满足了TestCondition
接口的条件,所有Test2 也被注册到了spring容器中,为了进一步验证结果,我们将Test组件删除掉,仅保留Test2 的注册,修改配置类如下
/**
* spring组件配置类,将Test组件删除掉
*/
@Configuration
public class TestConfiguration {
/**
* 根据TestCondition接口的条件判断向spring容器中注册Test2组件
*/
@Bean
@Conditional(TestCondition.class)
public Test2 test2() {
return new Test2();
}
}
重新运行spring引导类,控制台打印结果如下:
---hasTestBean : false
---Test bean names : []
---Test2 bean names : []
由此可见,当Test类在spring容器中没有注册时,不满足TestCondition
接口条件,所以Test2 组件也不会被注册到spring容器中。此时如果将test2()注册组件上的@Conditional
组件删除,Test2组件又会被正常注册到spring容器中。
上面的例子中是将@Conditional
注解添加到了方法上此时条件仅对当前方法生效,@Conditional
注解也可以加在类
上,此时条件对整个类中的组件注册均生效。按照上面的案例,做出以下调整:
TestCondition
需要实现ConfigurationCondition
接口,用来对配置类做处理
当配置类上添加了@Conditional注解时,需要注意的是,Condition接口中的条件是控制配置类本身还是控制配置类中的所有组件,因此Spring Framework提供了ConfigurationCondition接口,并使用枚举值让我们自定义选择。
enum ConfigurationPhase {
/**
* Condition接口中的条件控制着配置类本身的注册,当条件不匹配时,不会添加@configuration类
*/
PARSE_CONFIGURATION,
/**
* 控制Condition接口中的条件是对配置类中的组件进行解析,不会影响配置类本身的注册
*/
REGISTER_BEAN
}
TestCondition
接口实现修改如下
public class TestCondition implements ConfigurationCondition {
/**
* 当IOC容器中包含 Test的bean定义信息时,条件成立
*
* @param context 条件判断的上下文环境
* @param metadata 正在检查的类或方法的元数据
* @return 条件是否成立
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
boolean hasTestBean = ArrayUtils.isNotEmpty(testBeanNames);
System.out.println("---hasTestBean : " + hasTestBean);
return hasTestBean;
}
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
}
- 对javaBean类和配置类进行调整
@Data
@Component // 通过@Component注解直接注册 Test 组件
public class Test {
private String id = "@Bean";
}
@Data
public class Test2 {
private String id = "Test2: @Conditional";
}
@Data
public class Test3 {
private String id = "Test3: @Conditional";
}
- 配置类调整如下,在配置类上添加
@Conditional
注解
/**
* spring组件配置类,根据TestCondition接口的条件判断向spring容器中注册Test2,Test3组件
*/
@Configuration
@Conditional(TestCondition.class)
public class TestConfiguration {
@Bean
public Test3 test3() {
return new Test3();
}
@Bean
public Test2 test2() {
return new Test2();
}
}
- 引导类中添加对
Test3
类型的查询
String[] names = applicationContext.getBeanNamesForType(Test3.class);
System.out.println("---Test3 bean names : " + Arrays.asList(names));
启动引导类,控制台打印结果如下:
---hasTestBean : true
---Test bean names : [test]
---Test2 bean names : [test2]
---Test3 bean names : [test3]
由此可见,当TestCondition
接口条件匹配时,Test2,Test3均被注册到spring容器中,如果将Test组件不进行注册,我们看看下面的结果。
- 将
Test
类上的@Component
注解删除,其余代码均不变
@Data
public class Test {
private String id = "@Bean";
}
重新启动引导类,打印结果如下:
---hasTestBean : false
---Test bean names : []
---Test2 bean names : []
---Test3 bean names : []
由此可以看出,TestCondition
的条件控制着配置类中的组件注册
使用 @Conditional 的注意点
- 当
@Conditional
注解加在方法上时,可以直接使用Condition
接口进行实现,通过条件匹配,判断组件是否可以被注册到spring容器中 - 当
@Conditional
注解加在配置类上时,需要使用ConfigurationCondition
接口进行实现,通过ConfigurationPhase
来指定条件匹配对配置类本身注册的影响。因为Condition
接口的条件是在spring扫描候选组件的过程中执行的,所以在根据Bean进行条件判断时,需要注意此问题。如果是自定义的业务需求判断,不会受此影响。
总结
我们平常在使用spring或者spring MVC时,@Conditional 注解的使用可能并不是很多,但是在当下Spring Boot大行其道,并且Spring Boot对@Conditional进行了很多的扩展,所以了解@Conditional的使用及原理,也是对Spring Boot的基础学习做更多的铺垫。
本文对@Conditional的使用进行了介绍,没有深入说明Condition的原理,这些内容将在后续的spring组件扫描过程中进行说明。
学习永远都不是一件简单的事情,可以有迷茫,可以懒惰,但是前进的脚步永远都不能停止。
不积跬步,无以至千里;不积小流,无以成江海;
Spring Framework 条件装配 之 @Conditional的更多相关文章
- spring的条件装配bean
1 应用程序环境的迁移 问题: 开发软件时,有一个很大的挑战,就是将应用程序从一个环境迁移到另一个环境. 例如,开发环境中很多方式的处理并不适合生产环境,迁移后需要修改,这个过程可能会莫名的出现很多b ...
- Spring Framework自动装配setAutowireMode和Mybatis案例的源码探究
由前文可得知, Spring Framework的自动装配有两种方式:xml配置和注解配置: 自动装配的类型有: (1)xml配置中的byType根据类型查找(@Autowired注解是默认根据类型查 ...
- 一步步从Spring Framework装配掌握SpringBoot自动装配
目录 Spring Framework模式注解 Spring Framework@Enable模块装配 Spring Framework条件装配 SpringBoot 自动装配 本章总结 Spring ...
- Spring Boot之从Spring Framework装配掌握SpringBoot自动装配
Spring Framework模式注解 模式注解是一种用于声明在应用中扮演“组件”角色的注解.如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的 ...
- Spring Boot 自动装配(一)
目录 目录 前言 1.起源 2.Spring 模式注解 2.1.装配方式 2.2.派生性 3.Spring @Enable 模块驱动 3.1.Spring框架中@Enable实现方式 3.2.自定义@ ...
- 【10分钟学Spring】:@Profile、@Conditional实现条件化装配
根据不同的环境来装配不同的bean 企业级开发中,我们一般有多种环境,比如开发环境.测试环境.UAT环境和生产环境.而系统中有些配置是和环境强相关的,比如数据库相关的配置,与其他外部系统的集成等. 如 ...
- Spring Boot 2 实践记录之 条件装配
实验项目是想要使用多种数据库访问方式,比如 JPA 和 MyBatis. 项目的 Service 层业务逻辑相同,只是具体实现代码不同,自然是一组接口,两组实现类的架构比较合理. 不过这种模式却有一个 ...
- Spring系列(零) Spring Framework 文档中文翻译
Spring 框架文档(核心篇1和2) Version 5.1.3.RELEASE 最新的, 更新的笔记, 支持的版本和其他主题,独立的发布版本等, 是在Github Wiki 项目维护的. 总览 历 ...
- Spring Boot 自动装配(二)
目录 目录 前言 1.起源 2.Spring Boot 自动装配实现 2.1.@EnableAutoConfiguration 实现 2.1.1. 获取默认包扫描路径 2.1.2.获取自动装配的组件 ...
随机推荐
- c#-WPF string,color,brush之间的转换
原文:c#-WPF string,color,brush之间的转换 String转换成Color string-"ffffff" Color color = (Color)Colo ...
- easyui的datebox最简单的方法来格式化
看了网上有很多解决方案,我也写了一个比较简单的方法. 实现easyui的datebox格式化. 效果例如以下.用"++"隔开,看你喜欢用什么都能够. 1.html <span ...
- 关于idea maven工程创建struts2入门配置及案例
1.在maven工程下添加需要导入的jar包节点 <dependencies> <dependency> <groupId>org.apache.struts< ...
- dedecms织梦出现“Upload filetype not allow”原因和解决方案
资源:武汉科技有限公司倍频猫 我最近升级DEDE5.6出现此问题后.和解决的方法. 假设有同学是按某些朋友用删除代码的方法解决这个问题的话,建议你修正此做法,毕竟这是有非常大安全风险的.而DEDE本身 ...
- 宽字符std::wstring的长度和大小问题?sizeof(std::wstring)是固定的32,说明std::wstring是一个普通的C++类,而且和Delphi不一样,没有负方向,因为那个需要编译器的支持
std::wstring ws=L"kkkk"; int il=ws.length(); int ia=sizeof(ws); int ib=sizeof(&qu ...
- 自定义LISTBOX内子项为checkbox或者radio时,关于IsChecked绑定
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}&q ...
- ArcGIS中Python逆地理编码,根据坐标获取实际的地址
import json import urllib import arcpy def getAddress(lng,lat): url= 'http://restapi.amap.com/v3/geo ...
- MS SQL SERVER搜索某个表的主键所在的列名
原文:MS SQL SERVER搜索某个表的主键所在的列名 SELECT SYSCOLUMNS.name FROM SYSCOLUMNS,SYSOBJECTS,SYSINDEXES,SYSINDEX ...
- 零元学Expression Blend 4 - Chapter 2 入门界面简介
原文:零元学Expression Blend 4 - Chapter 2 入门界面简介 在这篇教学我将会介绍Expression Blend 4的基本界面,虽然有些网站已经有做了介绍,为了整个教学的完 ...
- 在UWP的XAML中使用原始类型
问题: I'm trying to access the system namespace for StaticResource variables in XAML on UWP. Here's (m ...