本文共 9394 字,大约阅读时间需要 31 分钟。
当通过接口的方式注入Bean时,如果有多个子类的bean存在时,具体哪个bean会被注入呢?系统中能否存在两个重名的bean呢?如果可以,那么怎么选择引入呢?如果不行的话又该怎么避免上面的问题呢?
<!-- more -->
I. 多实例Bean的选择
这个场景可以说是比较常见的,现在提倡面向接口编程嘛,当一个接口有多个实例时,怎么注入和引用就需要我们额外关注下了一个输出的接口定义如下
public interface IPrint { void print(String msg);}
对应给两个实现
@Componentpublic class ConsolePrint implements IPrint { @Override public void print(String msg) { System.out.println("console print: " + msg); }}@Slf4j@Componentpublic 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@Componentpublic 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
根据上面的作用说明,很明显可以得知一点
@Primary注解的使用有唯一性要求:即对应上面的case,一个接口的子类中,只能有一个实现上有这个注解
假设将这个注解放在LogPrint上之后,如下
@Slf4j@Component@Primarypublic class LogPrint implements IPrint { @Override public void print(String msg) { log.info("log print: {}", msg); }}
结合上面的常用姿势,加上这个注解之后,我们的测试用例应该至少包含下面几个
@Resource 指定beanName的是否会被@Primary影响
前面的@Autowired注解 + 属性名的方式,是按照第一节的方式选择呢,还是选择被@Primary标识的实例@Autowired + 随意的一个非beanName的属性,验证是否会选中@Primary标识的注解@Componentpublic 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]
存在@Primary注解时
@Resource注解指定name时,根据name来查找对应的bean
@Resource注解指定name时,根据name来查找对应的bean
A的服务,依赖B和C的服务;而B和C是两个完全独立的第三方服务,他们各自都提供了一个beanName=xxxService的bean,对于A而言,Spring容器中就会有BeanName冲突的问题了,而且这种场景,对A而言,也是不可控的啊,这种情况下改怎么办?
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.
@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
@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. */@Configurationpublic class AutoConfig { @Bean public SameA mySameA() { return new SameA(); }}
其他的代码和之前没有区别,再次执行,结果如下, 最后的输出为 a sameA!,根据类型来选择了实例化的bean了
觉得不错请点赞支持,欢迎留言或进我的个人群855801563领取【架构资料专题目合集90期】、【BATJTMD大厂JAVA面试真题1000+】,本群专用于学习交流技术、分享面试机会,拒绝广告,我也会在群内不定期答题、探讨。
转载于:https://blog.51cto.com/13981400/2366121