Field injection is not recommended – Spring IOC & Spring 三种依赖注入方式

运行 IDE 的自动检查工具分析代码时, 如果用@Autowired 注解的话,会提示如下的警告:


FieldInjectionIsNotRecommended.png

第一次看到这样的提示,是很困惑的,因为通常情况下直接使用@Autowired 不仅让代码更加简洁易读,写起来也十分的方便。

虽然最新(5.1.9)的Spring 文档依赖注入的章节里只介绍了两种依赖注入的方法,但实际上有三种依赖注入的方式:

  1. Constructor-based dependency injection(基于构造方法的依赖注入)
  2. Setter-based dependency injection(基于 setter 的依赖注入)
  3. Field-based dependency injection(基于 filed 注解的依赖注入)

第三种依赖注入的方式是代码分析工具不建议的,但也是使用最多、最常见的依赖注入方式。即使在 Spring 官方的一些手册里(比如Accessing data with MySQL),也可以看到使用 Field-based dependency injection。

下面分别具体介绍一下这三种依赖注入的方式。

三种依赖注入的方式

  • Constructor-based dependency injection

    @Component
    public class ConstructorBasedInjection {
    
        private final InjectedBean injectedBean;
    
        @Autowired
        public ConstructorBasedInjection(InjectedBean injectedBean) {
            this.injectedBean = injectedBean;
        }
    
    }
    

    基于构造方法的依赖注入的主要优点是,可以注入声明为 final 的字段。
    基于构造方法的依赖注入在类实例化期间启动,对于必要的依赖项来说,使用构造方法注入会更合适。

  • Setter-based dependency injection

    @Component
    public class ConstructorBasedInjection {
    
        private InjectedBean injectedBean;
    
        @Autowired
        public void setInjectedBean(InjectedBean injectedBean) {
            this.injectedBean = injectedBean;
        }
    
    }
    

    如果使用无参数构造方法或无参数静态工厂方法实例化 Bean,Spring 容器将调用这些 setter 方法,注入 Bean 的依赖项。

  • Field-based dependency injection

    @Component
    public class ConstructorBasedInjection {
    
        @Autowired
        private InjectedBean injectedBean;
    
    }
    

    使用基于字段的依赖注入的话,只需在需要注入的字段加上@Autowired,Spring 容器就会在类初始化的时候设置这些字段。
    可以看到,这种方法确实是最简洁的,不需要任何模版代码。但是为什么代码检查还是还是不建议这种方法呢?因为它确实存在着一些缺点。

Field-based dependency injection 的缺点

  • 不允许 Immutable 字段的声明

    基于字段的依赖注入不支持声明为 final 的字段,声明为 final 的字段必须在类初始化的时候初始化该字段。如果声明了 final 的字段并想注入该依赖,唯一的方式是使用基于构造方法的注入

  • 可能会违反单一职责原则

    在面向对象的设计原则中,我们经常会提到 SOLID,更好地遵循 SOLID 原则能让我们的代码更好理解、维护,拓展性更强。其中 S 指的是单一职责原则,也就是一个类应该只负责整个工程的单个功能部分。

    如果使用基于字段依赖注入的话,即使这个类依赖了很多其它的类,也经常觉得没什么问题。而如果使用基于构造方法的依赖注入的话,会很容易发现构造方法传入了太多的参数(有的人觉得这是基于构造方法依赖注入方法的缺点,事实上出现这种情况时,是一个代码需要重构的提醒),这个时候我们可以审视我们的代码,是否需要将该类拆分重构。

    使用基于字段的依赖注入虽然没有直接违背单一职责原则,但确实隐藏了发现违背单一职责原则的一些信号。

  • 与依赖注入的结合过分紧密

    使用基于字段的依赖注入的主要原因是能够减少代码,让代码简洁。但这同时也意味着设置这些字段的唯一方法是通过 Spring 容器实例化类并使用反射注入它们,否则字段将不会初始化,该类也无法使用。

    如果要在 Spring 容器外部使用这些类,比如单元测试,则必须使用 Spring 容器来实例化类,没有其他方法(除了反射)来设置这些字段。
    而如果使用基于构造方法或者基于 setter 依赖注入时,在单元测试时,我们可以 mock 依赖的对象,并将它们传到构造方法或 setter 方法中,会让单元测试简单很多。

  • 隐藏了依赖关系

    使用基于构造方法依赖注入,或者基于 setter 的依赖注入时,外部可以通过构造方法或者 setter 方法知道该类的依赖项。
    而如果使用基于字段的依赖注入时,该类的所有依赖对于外部来说是不可知的。

结论

基于字段的依赖注入虽然用起来十分方便,代码也十分简洁,但确实存在着一些缺点,这也是为什么代码检查工具不推荐这种写法的原因。
通常情况下,如果有 final 字段,或者有必需的依赖项,建议使用基于构造方法的依赖注入。基于 setter 的依赖注入通常建议用来注入可选的依赖项。

你可能感兴趣的:(Field injection is not recommended – Spring IOC & Spring 三种依赖注入方式)