条件装配是Spring Boot
一大特点,根据是否满足指定的条件来决定是否装配 Bean ,做到了动态灵活性,starter的自动配置类中就是使用@Conditional及其衍生扩展注解@ConditionalOnXXX做到了自动装配的,所以接着之前总结的 Spring Boot自动配置原理和自定义封装一个starter,今天分析一下starter中自动配置类的条件装配注解。
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:https://github.com/plasticene/plasticene-boot-starter-parent
Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
微信公众号:Shepherd进阶笔记
@Conditional:该注解是在spring4中新加的,其作用顾名思义就是按照一定的条件进行判断,满足条件才将bean注入到容器中,注解源码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
从代码中可知,该注解可作用在类,方法上,同时只有一个属性value,是一个Class数组,并且需要继承或者实现Condition接口:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
@FunctionalInterface:表示该接口是一个函数式接口,即可以使用函数式编程,lambda表达式。
Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。
总结:@Conditional注解通过传入一个或者多个实现了的Condition接口的实现类,重写Condition接口的matches方法,其条件逻辑在该方法之中,作用于创建bean的地方。
根据上面的描述,接下来我模拟多语言环境条件装配切换不同语言的场景:
语言类
@Data
@Builder
public class Language {
private Long id;
private String content;
}
条件:
public class ChineseCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("lang");
if (Objects.equals(property, "zh_CN")) {
return true;
}
return false;
}
}
public class EnglishCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("lang");
if (Objects.equals(property, "en_US")) {
return true;
}
return false;
}
}
配置类:
@Configuration
public class MyConfig {
@Bean
@Conditional(ChineseCondition.class)
public Language chinese() {
return Language.builder().id(1l).content("华流才是最屌的").build();
}
@Bean
@Conditional(EnglishCondition.class)
public Language english() {
return Language.builder().id(2l).content("english is good").build();
}
public static void main(String[] args) {
System.setProperty("lang", "zh_CN");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
// 遍历Spring容器中的beanName
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
执行结果:chinese
,说明根据条件匹配到ChineseCondition
返回true,成功注入bean。
@ConditionalOnBean :当给定的在bean存在时,则实例化当前Bean,示例如下
@Bean
@ConditionalOnBean(name = "address")
public User (Address address) {
//这里如果address实体没有成功注入 这里就会报空指针
address.setCity("hangzhou");
address.setId(1l)
return new User("魅影", city);
}
这里加了ConditionalOnBean注解,表示只有address这个bean存在才会实例化user
实现原理如下:
@ConditionalOnMissingBean:当给定的在bean不存在时,则实例化当前Bean, 与@ConditionalOnBean相反
@Configuration
public class BeanConfig {
@Bean(name = "notebookPC")
public Computer computer1(){
return new Computer("笔记本电脑");
}
@ConditionalOnMissingBean(Computer.class)
@Bean("reservePC")
public Computer computer2(){
return new Computer("备用电脑");
}
ConditionalOnMissingBean无参的情况,通过源码可知,当这个注解没有参数时,仅当他注解到方法,且方法上也有@Bean,才有意义,否则无意义。那意义在于已被注解方法的返回值类型的名字作为ConditionalOnMissingBean的type属性的值。
@ConditionalOnClass:当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass:当给定的类名在类路径上不存在,则实例化当前Bean
@ConditionalOnProperty:Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
// 数组,获取对应property名称的值,与name不可同时使用
String[] value() default {};
// 配置属性名称的前缀,比如spring.http.encoding
String prefix() default "";
// 数组,配置属性完整名称或部分名称
// 可与prefix组合使用,组成完整的配置属性名称,与value不可同时使用
String[] name() default {};
// 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
String havingValue() default "";
// 缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效
boolean matchIfMissing() default false; // 是否可以松散匹配,至今不知道怎么使用的 boolean relaxedNames() default true;}
通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。
如果该值为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该configuration不生效;为true则生效。
当然havingValue也可以不设置,只要配置项的值不是false或“false”,都加载Bean
示例代码:
feign:
hystrix:
enabled: true
fegin开启断路器hystrix:
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
结论:@Conditional及其衍生注解,是为了方便程序根据当前环境或者容器情况来动态注入bean,是Spring Boot
条件装配实现的核心所在。