当通过接口的方式注入 Bean 时, 如果有多个子类的 bean 存在时, 具体哪个 bean 会被注入呢? 系统中能否存在两个重名的 bean 呢? 如果可以, 那么怎么选择引入呢? 如果不行的话又该怎么避免上面的问题呢? 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 了 ![](https://s1.51cto.com/images/blog/201903/20/05732fbf56e59b588f9027b37bc15b8a.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
来源: http://www.bubuko.com/infodetail-2993965.html