2

spring接口多实现类,该依赖注入哪一个? - 字母哥博客

 1 year ago
source link: https://www.cnblogs.com/zimug/p/16618442.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、问题的描述

在实际的系统应用开发中我经常会遇到这样的一类需求,相信大家在工作中也会经常遇到:

  • 同一个系统在多个省份部署。
  • 一个业务在北京是一种实现方式,是基于北京用户的需求。
  • 同样的业务在上海是另外一种实现方式,与北京的实现方式大同小异

遇到这样的需求,我们通常会定义一个业务实现的接口,比如:

public interface IDemoService {
  public void doSomething();
}

在北京环境下这样实现,比如:

@Component
public class DemoServiceBeijing implements IDemoService {
  @Override
  public void doSomething() {System.out.println("北京的业务实现");}
}

在上海环境下这样实现,比如:

@Component
public class DemoServiceShanghai implements IDemoService {
  @Override
  public void doSomething() {System.out.println("上海的业务实现");}
}

然后我们写一个模拟业务测试用例

@SpringBootTest
class DemoApplicationTests {
    //这里注入的demoService是DemoServiceShanghai,还是DemoServiceBeijing?
    @Resource
    IDemoService demoService;  
    @Test
    void testDemoService() {
        demoService.doSomething();
    }
}

当我们执行这个测试用例的时候一定会报错,因为Spring发现了两个IDemoService的实现类。它不知道去实例化哪一个实现类,来作为IDemoService的实际业务处理bean。当然我们期望的状态是:

  • 在北京部署系统的时候,使用DemoServiceBeijing作为IDemoService的实现类完成依赖注入
  • 在上海部署系统的时候,使用DemoServiceShanghai作为IDemoService的实现类完成依赖注入

二、相对低级解决方案

面对上面的需求,先说几个相对低级的解决方案,这几个方案虽然可以实现我们期望的状态,但是对运维不够友好。

2.1. 方案一:使用@Primary注解

假如在北京部署系统的时候,在DemoServiceBeijing的类上面加上@Primary,该注解的作用就是强迫从多个实现类里面选一个实现类,如果Spring不知道选哪一个,我们告诉它一个默认的。

@Primary
@Component
public class DemoServiceBeijing implements IDemoService {

2.2. 方案二:使用@Resource注解

因为Resource注解默认使用名称进行依赖注入,所以变量名明确叫做demoServiceBeijing(首字母小写),使用的就是DemoServiceBeijing实现类。

@Resource
IDemoService demoServiceBeijing;  //这里的变量名称指定了bean名称
//IDemoService demoService;  被替换掉
@Resource(name = "demoServiceBeijing")  //使用resource注解明确指定名称
IDemoService demoService;

2.3.方案三:使用@Qualifier注解

与上文同样的道理,使用@Qualifier注解,指定bean的名称进行依赖注入

@Qualifier("demoServiceBeijing")  //使用Qualifier注解明确指定名称
@Resource
IDemoService demoService;

上面所提到的三个方案虽然都可以解决:在不同的部署环境下使用不同的接口实现类完成依赖注入的问题。但是这样不好,因为一旦我们要把部署环境从beijing(北京)换成shanghai(上海),就需要把上面的注解的位置或者内容全都修改一遍(所有的实现类代码都要修改)。

三、相对高级的解决方案

我们提出进一步的期望:就是只修改一个配置就能完成部署环境切换的操作。比如:

deploy:
  province: beijing

当我们期望把部署环境从北京切换到上海的时候,只需要将上文配置中的beijing 改成 shanghai ,这该怎么实现呢?

  • 在北京的实现类上面加上ConditionalOnProperty注解,havingValue的值为beijing
@Component
@ConditionalOnProperty(value="deploy.province",havingValue = "beijing")
public class DemoServiceBeijing implements IDemoService {
  • 在上海的实现类上面加上ConditionalOnProperty注解,havingValue的值为shanghai
@Component
@ConditionalOnProperty(value="deploy.province",havingValue = "shanghai")
public class DemoServiceShanghai implements IDemoService {

ConditionalOnProperty注解在这里的作用就是:读取配置文件发现deploy.province,并将该配置的值与havingValue匹配,匹配上哪一个就实例化哪一个类作为该接口的实现类bean注入到Spring容器中(当然注入过程需要配合@Component注解实现)。

欢迎关注我的公告号:字母哥杂谈,回复003赠送作者专栏《docker修炼之道》的PDF版本,30余篇精品docker文章。字母哥博客:zimug.com


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK