SpringBoot基础篇之重名Bean的解决与多实例选择
当通过接⼝的⽅式注⼊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
@Resource注解指定name时,根据name来查对应的bean
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.
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.
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.
@Autowiredresource和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 exc
at t.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:184) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.REL    at t.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316) ~[sprin    at t.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~    at t.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.j    at t.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~
[    at t.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5    at t.fresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.REL    at org.springframework.fresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.freshContext(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: t.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'sameA' for bean class [com.git.hui.boo    at t.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:348) ~[spring-context-5.0.8    at t.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:286) ~[spring-context-5.0.8.RELEA    at t.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.0.8.RELEA    at t.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:288) ~[spring-context-5.0.8.    at t.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-5.0.8.RE    at t.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:202) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.REL    at t.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:170) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.REL    ... 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 t.annotation.Bean;
import t.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了
觉得不错请点赞⽀持,欢迎留⾔或进我的个⼈855801563领取【架构资料专题⽬合集90期】、【BATJTMD⼤⼚JAVA⾯试真题1000+】,本专⽤于学习交流技术、分享⾯试机会,拒绝⼴告,我也会在内不定期答题、探讨。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。