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组件扫描过程中进行说明。
学习永远都不是一件简单的事情,可以有迷茫,可以懒惰,但是前进的脚步永远都不能停止。
不积跬步,无以至千里;不积小流,无以成江海;
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一篇文章教你如何捕获前端错误
本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/E51lKQOojsvhHvACIyXwhw 作者:黄文佳 常见错误的分类 对于用户在访问页面时发生的错误,主要包括以下几个类型: 1、js运行时错误 JavaScript代码在用户浏览器中执行时,由于一些边界情况、本地环境的不可控等因素,可能会存在js运行时错误。 而依赖客户端的某些方法,由于兼容性或者网络等问题,也有概率会出现运行时错误。 e.g: 下图是当使用了未定义的变量"foo",导致产生js运行时错误时的上报数据: 2、资源加载错误 这里的静态资源包括js、css以及image等。现在的web项目,往往依赖了大量的静态资源,而且一般也会有cdn存在。 如果某个节点出现问题导致某个静态资源无法访问,就需要能够捕获这种异常并进行上报,方便第一时间解决问题。 e.g: 下图是图片资源不存在时的上报数据: 3、未处理的promise错误 未使用catch捕获的promise错误,往往都会存在比较大的风险。而编码时有可能覆盖的不够全面,因此有必要监控未处理的promise错误并进行上报。...
- 下一篇
Python3入门(九)输入和输出
前面几章介绍了一些常用的输入输出,本文将具体介绍Python的输入和输出 一、输出格式美化 Python两种输出值的方式: 表达式语句和print()函数。第三种方式是使用文件对象的write()方法,标准输出文件可以用sys.stdout引用。如果你希望输出的形式更加多样,可以使用str.format()函数来格式化输出值。如果你希望将输出的值转成字符串,可以使用repr()或str()函数来实现: str():返回一个用户易读的表达形式 repr():返回一个解释器易读的表达形式,可以转义字符串中的特殊字符,参数可以是python中的任何对象 例子: for x in range(1, 5): print(repr(x).rjust(1), repr(x * x).rjust(2), repr(x * x * x).rjust(3
相关文章
文章评论
共有0条评论来说两句吧...