一个 Spring 依赖注入的问题浅析
使用 Spring 做项目的先思考一个问题,一个接口有在多个实现类的情况下,在成员变量的声明上如果没有指定注入那个别名的 Bean 的时候 Spring 会如何选择对应的 Bean 来进行注入?
问题模拟
先来简单说明一下示例代码。
定义一个接口,用于打印当前注入的 Bean 的名称
1 | public interface TestService { |
针对上面定义的接口编写两个实现类,并分别定义其 beanName 为 testService 和 testService2
1 |
|
编写 Handler,并注入 TestService
1 |
|
编写单元测试,并观察输出
1 |
|
执行该单元测试,发现其打印的 beanName 为 testService。从输出结果可以看出在这种情况下最终注入的是 beanName 为 testService 的 bean,与组合交易子系统的现象一致。
源码分析与验证
根据 Spring 的文档,使用 Autowired 注解在自动装配时会通过 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties 方法来对属性进行赋值操作。我们可以在该方法入口打上断点,并对断点指定条件,如下图:
由于方法深度较深,调试的过程这里不再赘述,我们直接分析影响我们最终结果的方法 org.springframework.beans.factory.support.DefaultListableBeanFactory#determineAutowireCandidate,其源码及分析如下:
1 | /** |
下面我们来验证一下源码中的我们未验证到的情况
使用 Primary 注解来标识主要的类
将 beanName 为 testService2 的 Bean 加上 Primary 注解并运行单元测试,其打印的 beanName 为 testService2。
使用 Priority 来标识 Bean 的优先级
去掉上面操作所添加的 Primary 注解,来验证使用 Priority 时的情况。
- 将 beanName 为 testService2 的 bean 加上 @Priority(2) 注解并运行单元测试,其打印的 beanName 为 testService2;
- 将 beanName 为 testService 的 bean 加上 @Priority(1) 注解,将 beanName 为 testService2 的 bean 加上 @Priority(2) 注解并运行单元测试,其打印的 beanName 为 testService;
- 将 beanName 为 testService 的 bean 加上 @Priority(3) 注解,将 beanName 为 testService2 的 bean 加上 @Priority(2) 注解并运行单元测试,其打印的 beanName 为 testService2。
修改 TestHandler 中的属性名
去掉上面操作所添加的 Primary 和 Priority 注解,来验证根据属性名注入时的情况。
- 将 TestHandler 中的属性名修改为 testService2 并运行单元测试,其打印的 beanName 为 testService2;
- 将 TestHandler 中的属性名修改为 testService3 并运行单元测试,程序抛出 NoUniqueBeanDefinitionException 异常。
最佳实践建议
在实际开发中,当遇到同一接口有多个实现类的情况时,推荐使用以下方式明确指定要注入的Bean:
使用 @Qualifier 注解
1 |
|
使用字段名匹配
1 |
|
使用 @Primary 注解
1 |
|
结论
根据上面源码的分析与实践,同一类型存在多个 Bean 的情况下,在使用 Autowired 注解时 Spring 会采用以下装配顺序来选择要装配的 Bean:
- 使用了 Primary 注解的 Bean;
- 查找使用了 Priority 注解的 Bean,并选择优先级最高的;
- 要注入的属性的属性名称与 Bean 名称相同的;
在我们平时写代码时,为了代码的可读性和明确性,建议使用 Qualifier 注解来明确指明要注入的Bean,避免依赖默认的装配规则。