SpringBoot基础篇之重名Bean的解决与多实例选择

简介: 当通过接口的方式注入Bean时,如果有多个子类的bean存在时,具体哪个bean会被注入呢?系统中能否存在两个重名的bean呢?如果可以,那么怎么选择引入呢?如果不行的话又该怎么避免上面的问题呢?

当通过接口的方式注入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了

image.png


相关文章
|
2月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
116 0
|
2月前
|
NoSQL Java 数据库
【问题篇】springboot项目通过数据库限制实例端口号
【问题篇】springboot项目通过数据库限制实例端口号
21 0
|
2月前
|
存储 NoSQL Java
Spring Boot统计一个Bean中方法的调用次数
Spring Boot统计一个Bean中方法的调用次数
37 1
|
7天前
|
XML Java 数据库
【SpringBoot:详解Bean装配】
【SpringBoot:详解Bean装配】
10 3
|
19天前
|
安全 Java Maven
[AIGC] Spring Boot中的切面编程和实例演示
[AIGC] Spring Boot中的切面编程和实例演示
|
25天前
|
Java Spring 容器
SpringBoot 使用Quartz执行定时任务对象时无法注入Bean问题
SpringBoot 使用Quartz执行定时任务对象时无法注入Bean问题
17 1
|
1月前
|
Java Spring 容器
SpringBoot中bean的生命周期
Spring Boot的Bean生命周期涉及实例化、属性注入、初始化和销毁。在实例化后,Spring通过构造函数或Setter注入属性,然后调用初始化方法(@PostConstruct、InitializingBean接口)。Bean在应用中使用后,当容器关闭时,会调用销毁方法(@PreDestroy、DisposableBean接口)。依赖注入、配置管理、组件扩展和切面编程是其常见应用场景。示例代码展示了如何通过实现BeanNameAware、BeanFactoryAware等接口以及使用@PostConstruct注解来控制Bean的初始化。
44 2
SpringBoot中bean的生命周期
|
2月前
|
Java 关系型数据库 MySQL
springboot+mybatis-plus实例demo
springboot+mybatis-plus实例demo
31 0
|
2月前
|
XML Java 数据格式
【springboot原理篇】Bean的加载方式,面试必看
【springboot原理篇】Bean的加载方式,面试必看
|
2月前
|
Java 容器 Spring
SpringBoot:Bean生命周期自定义初始化和销毁
SpringBoot:Bean生命周期自定义初始化和销毁