为了控制Bean的加载我使出了这些杀手锏
故事一: 绝代有佳人,幽居在空谷
美女同学小张,在工作中遇到了烦心事。心情那是破凉破凉的,无法言喻。
故事背景是最近由于需求变动,小张在项目中加入了MQ的集成,刚开始还没什么问题,后面慢慢问题的显露出来了。
自己在本地Debug的时候总是能消费到消息,由于历史原因,公司的项目只区分了两套环境,也就是测试和线上。本地启动默认就是测试环境,所以会消费测试环境的消息。
MQ的配置代码如下:
@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。代码如下:
@Configuration
@ConditionalOnProperty(name = "env", havingValue = "dev")
public class MqConfig {
@Bean(initMethod = "start", destroyMethod = "shutdown")
public ConsumerBean consumerBean() {
// ....
}
}
但这好像不符合小张同学的需求呀,需求是dev环境不加载才对。还有一个就是历史原因,增加一个环境有风险,因为对应的环境加载的内容什么的,都需要有变动,所以还是保留历史情况,环境不变,看能不能从其他的点解决这个问题。
现在面临的问题是不能增加新的环境,保留之前的test和prod。只需要在test和prod初始化Mq。
方案一:@ConditionalOnProperty
还是坚持使用@ConditionalOnProperty,既然不能通过环境来,我们可以单独增加一个属性来决定是否要启用Mq, 比如定义为:mq.enabled=true表示开启,mq.enabled=false表示不开启。
然后在test和prod启动的时候增加-Dmq.enabled=true或者在对应的配置文件中增加也可以,本地开发的时候-Dmq.enabled=false就可以了。
虽然能够解决问题,但是不是最佳的方案,因为已有的环境和开发人员本地都得增加启动参数。
方案二:继承SpringBootCondition自定义条件
可以使用@Conditional(MqConditional.class)注解,自定义一个条件类,在类中去判断是否要加载bean。
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'])}")
上面的表达式定义了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来决定是否要启用,将控制权交给使用者。
@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这些框架中大量的自动配置,都有用到这些注解,我贴几个图给大家看看。
为了控制Bean的加载我使出了这些杀手锏的更多相关文章
- 如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇
1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功 ...
- spring源码学习之bean的加载(二)
这是接着上篇继续写bean的加载过程,好像是有点太多了,因为bean的加载过程是很复杂的,要处理的情况有很多,继续... 7.创建bean 常规的bean的创建时通过doCreateBean方法来实现 ...
- 【Spring】详解Spring中Bean的加载
之前写过bean的解析,这篇来讲讲bean的加载,加载要比bean的解析复杂些,该文之前在小编原文中有发表过,要看原文的可以直接点击原文查看,从之前的例子开始,Spring中加载一个bean的方式: ...
- Spring 系列教程之 bean 的加载
Spring 系列教程之 bean 的加载 经过前面的分析,我们终于结束了对 XML 配置文件的解析,接下来将会面临更大的挑战,就是对 bean 加载的探索.bean 加载的功能实现远比 bean 的 ...
- 【Spring源码深度解析学习系列】Bean的加载(六)
Bean的加载所涉及到的大致步骤: 1)转换对应beanName 为什么需要转换beanName呢?因为传入的参数可能是别名,也可能是FactoryBean,所以需要一系列的解析,这些解析内容包括如下 ...
- Spring源码分析(十一)bean的加载
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 经过前面的分析,我们终于结束了对XML配置文件的解析,接下来将会面临更大 ...
- Spring之23:AbstractBeanFactory,Bean的加载
<spring源码之:循环依赖> AbstractBeanFactory的作用:别名管理,单例创建与注册,工厂方法FactoryBean支持. 由图我们直接的看出,AbstractBean ...
- spring源码学习之bean的加载(一)
对XML文件的解析基本上已经大致的走了一遍,虽然没有能吸收多少,但是脑子中总是有些印象的,接下来看下spring中的bean的加载,这个比xml解析复杂的多.这个加载,在我们使用的时候基本上是:Bea ...
- Js控制iFrame切换加载网址
<html> <head> <title>Js控制 iFrame 切换加载网址</title> </head> <body> & ...
随机推荐
- Java类成员之内部类
内部类含义: 在Java中允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类. Inner class 一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称. Inner ...
- echarts圆饼图设置默认选中项并在中间显示文字
效果: 代码: var myChart = echarts.init(document.getElementById('quanshi-echarts-two')); option = { grid: ...
- i3s 一种开源的三维地理数据规范 简单解读
i3s,esri主推到ogc的一种三维开源GIS数据标准. 版权声明:原创.博客园/B站/小专栏/知乎/CSDN @秋意正寒 转载请标注原地址并声明转载: https://www.cnblogs.co ...
- TensorFlow——学习率衰减的使用方法
在TensorFlow的优化器中, 都要设置学习率.学习率是在精度和速度之间找到一个平衡: 学习率太大,训练的速度会有提升,但是结果的精度不够,而且还可能导致不能收敛出现震荡的情况. 学习率太小,精度 ...
- TensorFlow——tensorflow编程基础
0.tensorflow中的模型运行基础 tensorflow的运行机制属于定义和运行相分离,在操作层面可以抽象成两种:模型构建和模型运行. 在模型构建中的常见概念: 张量(tensor):数据,即某 ...
- js最简单的编写地点
1. 在哪里? 在浏览器的控制台. 2. 有什么作用? 方便快捷的测试纯js代码语句. 3. 如何使用? Google浏览器为例: 按 F12键 打开 开发者工具 (或者 浏览器工具栏 => ...
- cogs 2098. [SYOI 2015] Asm.Def的病毒 LCA 求两条路径是否相交
2098. [SYOI 2015] Asm.Def的病毒 ★☆ 输入文件:asm_virus.in 输出文件:asm_virus.out 简单对比时间限制:1 s 内存限制:256 M ...
- 如何编写Robot Framework测试用例1---(基本格式篇)
引子 我们使用符合Robot Framework规范的一种表格语法来编写测试用例.用例一般会是下面这个样子 这样的表格存储到一个文件中,就是一组测试用例.RF支持多种格式,如HTML,TSV,纯文本等 ...
- 异常java.lang.NoSuchMethodError: org.springframework.core.GenericTypeResolver.resolveTypeArguments(Ljava/lang/Class;Ljava/lang/Class;)[Ljava/lang/Class;
java.lang.NoSuchMethodError: org.springframework.core.GenericTypeResolver.resolveTypeArguments(Ljava ...
- 【python系统学习07】一张图看懂字典并学会操作
点击跳转 - 原文地址 数据类型 - 字典(dict) 目录: 一张图get字典 字典是什么 js的对象 字典长啥样 语法伪代码 示例demo 语法成像 字典怎么用 字典长度获取--len函数 提取字 ...