SpringBoot基础篇之重名Bean的解决与多实例选择
更多Spring文章,欢迎点击 一灰灰Blog-Spring专题
当通过接口的方式注入Bean时,如果有多个子类的bean存在时,具体哪个bean会被注入呢?系统中能否存在两个重名的bean呢?如果可以,那么怎么选择引入呢?如果不行的话又该怎么避免上面的问题呢?
<!-- more -->
I. 多实例Bean的选择
这个场景可以说是比较常见的,现在提倡面向接口编程嘛,当一个接口有多个实例时,怎么注入和引用就需要我们额外关注下了
1. 基本使用姿势
首先定义一个接口和两个简单的实现类,并演示一下我们通常的用法
一个输出的接口定义如下
public interface IPrint { void print(String msg); }
对应给两个实现
@Component public class ConsolePrint implements IPrint { @Override public void print(String msg) { System.out.println("console print: " + msg); } } @Slf4j @Component public class LogPrint implements IPrint { @Override public void print(String msg) { log.info("log print: {}", msg); } }
下面就是我们一般的引用方式
@Autowired
注解时,属性名即为默认的Bean名,如下面的logPrint
就是获取beanName=logPrint
的bean@Resource(name=xxx)
直接指定Bean的name,来唯一选择匹配的bean
@Component public class NormalPrintDemo { @Resource(name = "consolePrint") private IPrint consolePrint; @Autowired private IPrint logPrint; @PostConstruct public void init() { consolePrint.print(" console print!!!"); logPrint.print(" log print!!!"); } }
上面是两种常见的使用姿势,此外还可以借助@Primary
注解来声明默认的注入bean
2. @Primary
注解
这个注解就是为了解决当有多个bean满足注入条件时,有这个注解的实例被选中
根据上面的作用说明,很明显可以得知一点
@Primary
注解的使用有唯一性要求:即对应上面的case,一个接口的子类中,只能有一个实现上有这个注解
假设将这个注解放在LogPrint
上之后,如下
@Slf4j @Component @Primary public class LogPrint implements IPrint { @Override public void print(String msg) { log.info("log print: {}", msg); } }
结合上面的常用姿势,加上这个注解之后,我们的测试用例应该至少包含下面几个
@Resource
指定beanName的是否会被@Primary
影响- 前面的
@Autowired
注解 + 属性名的方式,是按照第一节的方式选择呢,还是选择被@Primary
标识的实例 @Autowired
+ 随意的一个非beanName的属性,验证是否会选中@Primary
标识的注解
@Component public class PrintDemoBean { @Resource(name = "logPrint") private IPrint print; /** * 下面的注解不指定name,则实例为logPrint */ @Autowired private IPrint consolePrint; // logPrint的选择,由@Primary注解决定 @Autowired private IPrint logPrint; // logPrint的选择,由@Primary注解决定 @Autowired(required = false) private IPrint xxxPrint; @PostConstruct public void init() { print.print("expect logPrint for [print]"); consolePrint.print(" expect logPrint for [consolePrint]"); logPrint.print("expect logPrint for [logPrint]"); xxxPrint.print("expect logPrint for [xxxPrint]"); } }
执行结果如下
2018-10-22 19:42:40.234 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect logPrint for [print] 2018-10-22 19:42:40.235 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect consolePrint for [consolePrint] 2018-10-22 19:42:40.235 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect logPrint for [logPrint] 2018-10-22 19:42:40.235 INFO 61966 --- [ main] c.g.h.b.b.choose.sameclz.LogPrint : log print: expect logPrint for [xxxPrint]
3. 小结
根据前面的执行,因此可以知晓,选择bean的方式如下
存在@Primary
注解时
@Resource
注解指定name时,根据name来查找对应的bean@Autowired
注解,全部都用@Primary
标识的注解@Primary
注解要求唯一(非广义的唯一性,并不是指只能用一个@Primary,具体看前面)
不存在@Primary
注解时
@Resource
注解指定name时,根据name来查找对应的bean@Autowired
注解时,根据属性名去查对应的Bean,如果查不到则抛异常;如果查到,那即是它了
II. 重名Bean的问题
在我们实际的业务开发中,有多个bean名为xxx的异常应该算是比较常见的,也就是说应该不能有两个bean叫同一个name;但考虑下下面这个场景
A的服务,依赖B和C的服务;而B和C是两个完全独立的第三方服务,他们各自都提供了一个beanName=xxxService
的bean,对于A而言,Spring容器中就会有BeanName冲突的问题了,而且这种场景,对A而言,也是不可控的啊,这种情况下改怎么办?
1. 同名Bean
先来个case演示下同名bean的情况,如下定义两个bean,除了包路径不一样外,类名相同,通过@Component
注解方式声明bean,因此两个bean的beanName都是SameA
package com.git.hui.boot.beanorder.choose.samename.a; import org.springframework.stereotype.Component; /** * Created by @author yihui in 21:32 18/10/22. */ @Component public class SameA { private String text ; public SameA() { text = "a sameA!"; } public void print() { System.out.println(text); } } package com.git.hui.boot.beanorder.choose.samename.b; import org.springframework.stereotype.Component; /** * Created by @author yihui in 21:33 18/10/22. */ @Component public class SameA { private String text; public SameA() { text = "B SameA"; } public void print() { System.out.println(text); } }
接下来测试下引用,是否有问题
package com.git.hui.boot.beanorder.choose.samename; import com.git.hui.boot.beanorder.choose.samename.a.SameA; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; /** * Created by @author yihui in 21:32 18/10/22. */ @Component public class SameDemo { @Autowired private SameA sameA; @PostConstruct public void init() { sameA.print(); } }
执行之后,毫不意外的抛出了异常,堆栈信息如下
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.git.hui.boot.beanorder.Application]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boot.beanorder.choose.samename.b.SameA] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.boot.beanorder.choose.samename.a.SameA] at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:184) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE] at com.git.hui.boot.beanorder.Application.main(Application.java:15) [classes/:na] Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boot.beanorder.choose.samename.b.SameA] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.boot.beanorder.choose.samename.a.SameA] at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:288) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:202) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:170) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE] ... 12 common frames omitted
2. 同名问题规避
如果真的出现了上面这个问题,该怎么解决呢?如果这些bean是我们可控的,最简单的方式就是不要同名,定义的时候指定beanName,如下
@Component("aSameA") public class SameA { private String text ; public SameA() { text = "a sameA!"; } public void print() { System.out.println(text); } }
如果完全不可控呢?正如前面说的两个第三方服务我都得依赖,但是他们有同名的bean,怎么破?
一个解决方法就是排除掉其中一个同名的bean的自动加载,采用主动注册的方式注册这个bean
排除自动扫描的bean的方式如下,在启动类添加注解@ComponentScan
并指定其中的excludeFilters
属性
@SpringBootApplication @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SameA.class)}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
然后自定义一个bean的配置类
package com.git.hui.boot.beanorder.choose.samename; import com.git.hui.boot.beanorder.choose.samename.a.SameA; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created by @author yihui in 22:14 18/10/22. */ @Configuration public class AutoConfig { @Bean public SameA mySameA() { return new SameA(); } }
其他的代码和之前没有区别,再次执行,结果如下, 最后的输出为 a sameA!
,根据类型来选择了实例化的bean了
II. 其他
a. 更多博文
基础篇
- 181009-SpringBoot基础篇Bean之基本定义与使用
- 181012-SpringBoot基础篇Bean之自动加载
- 181013-SpringBoot基础篇Bean之动态注册
- 181018-SpringBoot基础篇Bean之条件注入@Condition使用姿势
- 181019-SpringBoot基础篇Bean之@ConditionalOnBean与@ConditionalOnClass
- 181019-SpringBoot基础篇Bean之条件注入@ConditionalOnProperty
- 181019-SpringBoot基础篇Bean之条件注入@ConditionalOnExpression
- 181022-SpringBoot基础篇Bean之多实例选择
应用篇
b. 项目源码
- 工程:spring-boot-demo
- module: 008-beanorder
1. 一灰灰Blog
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 扫描关注
一灰灰blog
知识星球
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
如何解决JavaScript中0.1+0.2不等于0.3
console.log(0.1+0.2===0.3)// true or false?? 在正常的数学逻辑思维中,0.1+0.2=0.3这个逻辑是正确的,但是在JavaScript中0.1+0.2!==0.3,这是为什么呢?这个问题也会偶尔被用来当做面试题来考查面试者对JavaScript的数值的理解程度。 在JavaScript中的二进制的浮点数0.1和0.2并不是十分精确,在他们相加的结果并非正好等于0.3,而是一个比较接近的数字 0.30000000000000004 ,所以条件判断结果为 false。 那么应该怎样来解决0.1+0.2等于0.3呢? 最好的方法是设置一个误差范围值,通常称为”机器精度“,而对于Javascript来说,这个值通常是2^-52,而在ES6中,已经为我们提供了这样一个 属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断(0.1+0.2)-0.3小于 Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true...
- 下一篇
Spark Parquet file split
在实际使用 spark + parquet 的时候, 遇到了两个不解的地方: 我们只有一个 parquet 文件(小于 hdfs block size), 但是 spark 在某个 stage 生成了4个 tasks 来处理. 4个 tasks 中只有一个 task 处理了所有数据, 其他几个都没有处理数据. 这两个问题牵涉到对于 parquet spark 是如何来进行切分 partitions, 以及每个 partition 要处理哪部分数据的. 先说结论, spark 中, parquet 是 splitable 的, 代码见ParquetFileFormat#isSplitable. 那会不会把数据切碎? 答案是不会, 因为是以 spark row group 为最小单位切分 parquet 的, 这也会导致一些 partitions 会没有数据, 极端情况下, 只有一个 row group 的话, partitions 再多, 也只会一个有数据. 接下来开始我们的源码之旅: 处理流程 1. 根据 parquet 按文件大小切块生成 partitions: 在 FileSour...
相关文章
文章评论
共有0条评论来说两句吧...