故事一:绝代有佳人,幽居在空谷
美女同窗小张,在工做中遇到了烦心事。心情那是破凉破凉的,没法言喻。
故事背景是最近因为需求变更,小张在项目中加入了 MQ 的集成,刚开始还没什么问题,后面慢慢问题的显露出来了。
本身在本地 Debug 的时候老是能消费到消息,因为历史缘由,公司的项目只区分了两套环境,也就是测试和线上。本地启动默认就是测试环境,因此会消费测试环境的消息。
MQ 的配置代码以下:web
@Configuration public class MqConfig { @Bean(initMethod = "start", destroyMethod = "shutdown") public ConsumerBean consumerBean() { // .... } }
想要解决小张的问题,那么就必须得有第三个环境的区分,也就是增长一个本地开发环境,而后经过环境来决定是否须要初始化 MQ。
这个时候就能够用到 Spring Boot 为咱们提供的 Conditional 家族的注解了,@Conditional 注解会根据具体的条件决定是否建立 bean 到容器中, 以下图:
经过@ConditionalOnProperty 来决定 MqConfig 是否要加载,@ConditionalOnProperty 的 name 就是配置项的名称,havingValue 就是匹配的值,也就是在 application 配置中存在 env=dev 才会初始化 MqConfig。代码以下:spring
@Configuration @ConditionalOnProperty(name = "env", havingValue = "dev") public class MqConfig { @Bean(initMethod = "start", destroyMethod = "shutdown") public ConsumerBean consumerBean() { // .... } }
但这好像不符合小张同窗的需求呀,需求是 dev 环境不加载才对。还有一个就是历史缘由,增长一个环境有风险,由于对应的环境加载的内容什么的,都须要有变更,因此仍是保留历史状况,环境不变,看能不能从其余的点解决这个问题。
如今面临的问题是不能增长新的环境,保留以前的 test 和 prod。只须要在 test 和 prod 初始化 Mq。br/>方案一:@ConditionalOnProperty
仍是坚持使用@ConditionalOnProperty,既然不能经过环境来,咱们能够单独增长一个属性来决定是否要启用 Mq, 好比定义为:mq.enabled=true 表示开启,mq.enabled=false 表示不开启。
而后在 test 和 prod 启动的时候增长-Dmq.enabled=true 或者在对应的配置文件中增长也能够,本地开发的时候-Dmq.enabled=false 就能够了。
虽然可以解决问题,可是不是最佳的方案,由于已有的环境和开发人员本地都得增长启动参数。
方案二:继承 SpringBootCondition 自定义条件
可使用@Conditional(MqConditional.class)注解,自定义一个条件类,在类中去判断是否要加载 bean。app
public class MqConditional extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String env = environment.getProperty("env"); if (StringUtils.isBlank(env)) { return ConditionOutcome.noMatch("no match"); } if (env.equals("test") || env.equals("prod")) { return ConditionOutcome.match(); } return ConditionOutcome.noMatch("no match"); } }
方案三:继承 AnyNestedCondition 自定义条件
可使用@Conditional(MqAvailableCondition.class)注解,自定义一个条件类,在类中可使用其余的 Conditional 注解来进行判断,好比使用@ConditionalOnProperty。框架
@Order(Ordered.LOWEST_PRECEDENCE) public class MqAvailableCondition extends AnyNestedCondition { public MqAvailableCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(name = "env", havingValue = "test") static class EnvTest { } @ConditionalOnProperty(name = "env", havingValue = "prod") static class EnvProd { } }
方案四:@ConditionalOnExpression
支持 SpEL 进行判断,若是知足 SpEL 表达式条件则加载这个 bean。这个就至关灵活了,能够将须要知足的条件都写进来。
@ConditionalOnExpression("#{'test'.equals(environment['env']) || 'prod'.equals(environment['env'])}")ide
上面的表达式定义了 Spring Environment 中只要有 env 为 test 或者 prod 的时候就会初始化 MqConfig。这样一来老的启动命令都不用改变,本地开发的时候也不用增长参数,能够说是最佳的方案,由于改动的点变少了,出错的概率小,使用难度低。
故事二:北方有佳人,绝世而独立
美女小杨同窗最近也遇到了烦心事,虽然是女生,可是也工做了几年了。最近受到领导重用,让她搭一套 Spring Cloud 的框架给同事们分享一下。
她有个想法是将某些信息能够经过 Feign 或者 RestTemplate 进行传递,自然友好的方式就是在拦截器中统一实现。
若是在每一个服务中都写一份同样的代码,就显得很低级了,因此她将这两个拦截器统一写在一个模块中,做为 Spring Boot Starter 的方式引入。
问题一
遇到的第一个问题是这个模块引入了 Feign 和 spring-web 两个依赖,想作的通用一点,就是使用者可能会用 Feign 来调用接口,也可能会用 RestTemplate 来调用接口,若是使用者不用 Feign, 可是引入了这个 Starter 也会依赖 Feign。
因此须要在依赖的时候设置 Feign 的 Maven 依赖 optional=true,让使用者本身去引入依赖。学习
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <optional>true</optional> </dependency>
问题二
第二个问题是拦截器的初始化,若是不作任何处理的话两个拦截器都会被初始化,若是使用者没有依赖 Feign,那么就会报错,因此咱们须要对拦截器的初始化进行处理。
下面是默认的配置:测试
@Bean public FeignRequestInterceptor feignRequestInterceptor() { return new FeignRequestInterceptor(); } @Bean public RestTemplateRequestInterceptor restTemplateRequestInterceptor() { return new RestTemplateRequestInterceptor(); }
两个拦截器都是实现框架自带的接口,因此咱们能够在最外层使用@ConditionalOnClass 来判断若是项目中存在这个 Class 再装置配置。
第二层能够经过@ConditionalOnProperty 来决定是否要启用,将控制权交给使用者。rest
@Configuration @ConditionalOnClass(name = "feign.RequestInterceptor") protected static class FeignRequestInterceptorConfiguration { @Bean @ConditionalOnProperty("feign.requestInterceptor.enabled") public FeignRequestInterceptor feignRequestInterceptor() { return new FeignRequestInterceptor(); } } @Configuration @ConditionalOnClass(name = "org.springframework.http.client.ClientHttpRequestInterceptor") protected static class RestTemplateRequestInterceptorConfiguration { @Bean @ConditionalOnProperty("restTemplate.requestInterceptor.enabled") public RestTemplateRequestInterceptor restTemplateRequestInterceptor() { return new RestTemplateRequestInterceptor(); } }
故事三:本身去学习
文章里只根据案例讲了一个使用的方式,固然还有不少没有讲的,你们能够本身去尝试了解一些做用以及在什么场景可使用,像@ConditionalOnBean,@ConditionalOnMissingBean 等注解。
另外一种学习的方式就是鼓励你们去看一些框架的源码,特别在 Spring Cloud 这些框架中大量的自动配置,都有用到这些注解,我贴几个图给你们看看。code
热文推荐
农村小伙艰难的复工之路blog
这位RD同窗,你好像对JD有点误解!
好机会,我要帮女同事解决Maven冲突问题
上线前一个小时,dubbo这个问题可把我折腾惨了
若有收获,点个在看,诚挚感谢
尹吉欢我不差钱啊