Spring IOC注入方式

依赖注入的方式

Spring 有三种依赖注入的方式

  1. 基于属性的注入

这种注入方式就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。这是我平常开发中看的最多也是最熟悉的一种方式。

@Autowired PushTaskService pushTaskService;

  1. 基于setter方法的注入

通过对应变量的setXXX()方法以及在方法上面使用注解,来完成依赖注入。比如:

private static TaskGroupTemplateRepository taskGroupTemplateRepository; 
private static TaskGroupService taskGroupService; 
@Autowired public void setTaskGroupTemplateRepository(TaskGroupTemplateRepository taskGroupTemplateRepository,TaskGroupService taskGroupService){ ExcelListener2.taskGroupTemplateRepository = taskGroupTemplateRepository; ExcelListener2.taskGroupService = taskGroupService; }

说明:在 Spring 4.5 及更高的版本中,setXXX 上面的 @Autowired 注解是可以不写的。

  1. 基于构造方法的注入

将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如:

@Autowired public ExcelListener(@Qualifier("taskGroupService") TaskGroupService taskGroupService) { this.taskGroupService = taskGroupService; }

@Autowired是干啥的

我们一般开发需要注入属性的时候都会使用的这个注解@Autowired,跟这个注解类似的还有2个,@Resource, @Inject。Spring 支持使用@Autowired, @Resource, @Inject 三个注解进行依赖注入。我们先看一下有啥区别:

@Autowired为Spring框架提供的注解,可以理解是Spring的亲儿子。这里先给出一个示例代码

public interface IndexService {
    void sayHello();
}

@Service
public class IndexServiceImpl implements IndexService {
    @Override
    public void sayHello() {
        System.out.println("hello, this is IndexServiceImpl");
    }
}

@Service
public class IndexServiceImpl2 implements IndexService {
    @Override
    public void sayHello() {
        System.out.println("hello, this is IndexServiceImpl2");
    }
}

测试方法

@SpringBootTest public class Stest { @Autowired // @Qualifier("indexServiceImpl2") IndexService indexService; @Test void gooo() { Assertions.assertNotNull(indexService); indexService.sayHello(); } }

我们说明一下匹配bean的过程:

  1. 按照type在上下文中查找匹配,查找type为IndexService的bean

  2. 如果有多个bean,则按照name进行匹配

    • 如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配,查找name为indexServiceImpl2的bean

    • 如果没有,则按照变量名进行匹配。查找name为indexService的bean

  3. 匹配不到,则报错。(@Autowired(required=false),如果设置required为false(默认为true),则注入失败时不会抛出异常)

@Inject是干啥的

在Spring 的环境下,@Inject和@Autowired 是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor这个后置处理器来处理的。 这两个的区别,首先@Inject是Java EE包里的,在SE环境需要单独引入。另一个区别在于@Autowired可以设置required=false而@Inject并没有这个属性。也有的说@Inject是spring的干儿子。

@Resource是干啥的

@Resource是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource。 这个@Resource有2个属性name和type。在spring中name属性定义为bean的名字,type这是bean的类型。如果属性上加@Resource注解那么他的注入流程是:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。

  2. 如果指定了name,则从上下文中查找名称匹配的bean进行装配,找不到则抛出异常。

  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。

  4. 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

上面的内容都了解了之后我们接下来看为啥IDEA会有个warning提醒。IDEA 提示 Field injection is not recommended。warning提醒的注入方式就是第一种使用属性注解的方式进行注入。

属性注入优点

代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了方便开发人员进行代码的编写。

属性注入可能出现的问题
  • 问题1

基于 field 的注入,虽然不是绝对禁止使用,但是它可能会带来一些隐含的问题。来我们举个例子:

    @Autowired
    private Person person;
    private String company;

    public UserServiceImpl() {
        this.company = person.getCompany();
    }

初看起来好像没有什么问题,Person 类会被作为一个依赖被注入到当前类中,同时这个类的 company 属性将在初始化时通过person.getCompany() 方法来获得值。我们尝试运行一下就会发现报错了。其实类似这个问题有人在stackoverflow上提问过,点我跳转

Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException

复制代码

在执行this.company = person.getCompany();这段代码的时候报了空指针。 出现这个问题的原因是,Java 在初始化一个类时,是按照静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。所以在执行这个类的构造方法时,person 对象尚未被注入,它的值还是 null。

  • 问题2

使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,如果你一个类注入非常多的其它的对象,拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则。顺便我看了一下我们现在的业务代码这个问题在我们的项目代码中真的很常见。

  • 问题3

这种注入形式就会造成你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化。也就是类和依赖容器强耦合,不能在容器外使用。

spring建议

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.

其实大概的意思就是2句话

强制依赖就用构造器方式 可选、可变的依赖就用setter注入

spring对采用构造方法注入的说明

Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码结构问题,这个类可能承担了过多的责任。

spring对采用setter方法注入的说明

基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。


Spring IOC注入方式
https://shikai.info/archives/spring-ioc-injection-mode
作者
石 凯
发布于
2023年07月30日
许可协议