可锐资源网

技术资源分享平台,提供编程学习、网站建设、脚本开发教程

Spring Boot 通过这7种策略实现注入

环境:SpringBoot3.4.2



1. 简介

在开发中,常常会遇到有多个相同类型 Bean 的情况,这时 Spring 究竟如何精准找到合适的 Bean 进行注入呢?

当面临多个相同类型 Bean 时,Spring 不会随意选择,而是遵循一套严谨的策略,层层筛选。从Spring Boot3(Spring 6)开始将通过 7种 策略进行筛选Bean。

对于开发者而言,了解这些策略至关重要。它能帮助我们在复杂的应用架构中,更好地掌控 Bean 的注入行为,避免因 Bean 冲突导致的问题。同时,也能让我们在设计应用时,更加合理地规划 Bean 的定义和依赖关系,充分发挥 Spring 框架的强大功能,提升开发效率和代码质量。

接下来,我将详细的介绍这 7种 注入策略。

2.实战案例

准备环境

// 准备接口
public interface GreetingService {
  void greet(String name);
}
// 下面是该接口的3个实现
@Service
public class ChineseGreetingService implements GreetingService {
  @Override
  public void greet(String name) {
    System.out.println("你好, " + name + "!");
  }
}
@Service
public class EnglishGreetingService implements GreetingService {
  @Override
  public void greet(String name) {
    System.out.println("Hello, " + name + "!");
  }
}
@Service
public class FrenchGreetingService implements GreetingService {
  @Override
  public void greet(String name) {
    System.out.println("Bonjour, " + name + " !");
  }
}

下面这个类用来注入 GreetingService 接口

@Service
public class CommonService {
  private final GreetingService greetingService ;
  public CommonService(GreetingService greetingService) {
    this.greetingService = greetingService;
  }
  public void say() {
    this.greetingService.greet("Spring全家桶实战案例") ;
  }
}

当前Spring容器中有3个GreetingService接口类型的Bean,启动Spring容器后将看到如下的错误:

要求一个GreetingService bean,实际发现了3个。

接下来,我们将详细的介绍对于此种情况Spring是采用的哪 6 种策略处理此种情况。

2.1 查找@Primary

当容器中存在多个相同类型的bean时,Spring会优先选择带有 @Primary 注解的bean作为首选实现。如下代码:

@Service
@Primary
public class ChineseGreetingService implements GreetingService{}

我们将其中一个Bean使用@Primary,容器启动就正确了。而该ChineseGreetingService将被注入到CommonService。

若在Spring配置中存在多个带有@Primary注解的同类型bean,容器在自动装配时将因无法明确优先级而抛出
NoUniqueBeanDefinitionException,导致应用启动失败。如下示例:

@Service
@Primary
public class EnglishGreetingService implements GreetingService {}
@Service
@Primary
public class ChineseGreetingService implements GreetingService{}

再次启动容器,抛出如下错误:

2.2 查找@Fallback

当没有任何一个 Bean 被标注为 @Primary 时,Spring 容器会进一步检查是否存在唯一一个未被 @Fallback 注解标记的 Bean,并将其作为候选进行自动注入。

修改代码如下:

@Service
public class ChineseGreetingService implements GreetingService
@Service
@Fallback
public class EnglishGreetingService implements GreetingService
@Service
@Fallback
public class FrenchGreetingService implements GreetingService

我们将其中2个bean使用 @Fallback 注解,标记为候选bean,这样做以后,我们的容器就能成功的注入 ChineseGreetingService

注意:你只能留一个没有使用@Fallback的bean,否则启动还是报错。

2.3 通过名称匹配

当通过@Primary与@Fallback都不能正确的处理,那么接下来就要开始通过beanName进行匹配。修改代码如下(记得不要忘记将使用@Primary和@Fallback注解的地方都删除):

@Service
public class CommonService {
  private final GreetingService chineseGreetingService ;
  public CommonService(GreetingService chineseGreetingService) {
    this.chineseGreetingService = chineseGreetingService;
  }
  // ...
}

在代码中需要注入依赖的位置,将注入变量的名称修改为与目标 Bean 的名称(beanName)完全一致。

2.4 使用@Qualifier

若在上述检查后仍未找到合适的 Bean,Spring 容器会进一步检查目标注入字段是否标注了 @Qualifier 注解。修改代码如下:

@Service
public class CommonService {
  private final GreetingService greetingService ;
  public CommonService(@Qualifier("chineseGreetingService") GreetingService greetingService) {
    this.greetingService = greetingService;
  }
}

构造函数的参数上明确指定了将要注入哪个Bean。

思考,如果我们将代码修改如下,会注入谁?修改代码如下:

private final GreetingService englishGreetingService ;
public CommonService(@Qualifier("chineseGreetingService") GreetingService englishGreetingService) {
  this.englishGreetingService = englishGreetingService;
  System.err.println(this.englishGreetingService.getClass()) ;
}

最终控制输出:

class com.pack.ioc.multi_bean_strategy.ChineseGreetingService

注入的是由 @Qualifier 设置的bean。

下面我们再将代码修改如下:

@Service
public class CommonService {
  @Resource
  @Qualifier("chineseGreetingService")
  private GreetingService englishGreetingService ;


  @PostConstruct
  public void init() {
    System.err.println(this.englishGreetingService.getClass()) ;
  }
}

控制台输出:

class com.pack.ioc.multi_bean_strategy.EnglishGreetingService

字段注入与构造函数的注入是有一定区别的,所以我们这里需要注意。

2.5 使用@Priority

到这里还是没有合适的bean,那么就开始检查这些bean是否使用了@Priority注解(定义bean的优先级)。修改代码如下:

@Service
@Priority(1)
public class ChineseGreetingService
@Service
@Priority(2)
public class EnglishGreetingService
@Service
@Priority(3)
public class FrenchGreetingService

说明:@Priority 注解指定的值越小优先级就越高。所以,通过上面的修改后,将成功的注入ChineseGreetingService。

2.6 设置默认候选Bean

从Spring 6.2 起,可以通过 @Bean 注解配置 defaultCandidate 属性,该属性用来指定:在仅基于纯类型(plain type)且不考虑任何额外限定条件(例如是否匹配限定符 qualifier)的情况下,此 Bean 是否能成为其他 Bean 自动注入(autowired)的候选对象。

我们这里需要通过@Bean注册bean,修改代码如下:

@Bean(defaultCandidate = true)
GreetingService chineseGreetingService() {
  return new ChineseGreetingService() ;
}
@Bean(defaultCandidate = false)
GreetingService englishGreetingService() {
  return new EnglishGreetingService() ;
}
@Bean(defaultCandidate = false)
GreetingService frenchGreetingService() {
  return new FrenchGreetingService() ;
}

该属性值默认为true,将其它2个都置为false,这样我们也可以成功的注入bean(ChineseGreetingService)。

2.7 直接使用已注册的Bean

这最后一种比较特殊,我们可以将要被注入的对象提前的注册到BeanFactory中。直接上代码吧。我们首先将3个实现类恢复到初始状态,代码如下:

@Service
public class ChineseGreetingService
@Service
public class EnglishGreetingService
@Service
public class FrenchGreetingService
// CommonService修改
@Resource
private GreetingService greetingService ;
@PostConstruct
public void init() {
  System.err.println(this.greetingService.getClass()) ;
}

接下来,我们自定义 BeanFactoryPostProcessor

@Component
public class PackBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // 这里我们直接指定了只要是GreetingService类型的注入,那就直接注入EnglishGreetingService
    beanFactory.registerResolvableDependency(
      GreetingService.class, new EnglishGreetingService()) ;
  }
}

通过此种方式,不管你容器有多少个同类型的bean,都将只会注入这里指定的对象。

我们可以直接在代码中注入 HttpServletRequest,也是通过此种方式实现的。

以上就是当同类型的Bean有多个时Spring底层处理的7种实现策略。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言