51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

为什么不推荐 @Autowired用于字段注入?

你好,我是猿java。

作为 Java程序员,Spring绝对是使用频次最高的一个框架,灵活便捷的注入,给开发人员省去了不少的烦恼,今天主要分享一个 Spring 官方不太推荐的方式:@Autowired用于字段注入。

@Autowired警告 {#Autowired警告}

从 Spring4.0 开始,官方就不推荐 @Autowire用于字段的注入,如下图,当字段上增加 @Autowired注解时,ideal会出现告警"Field injection is not recommended",全部内容如下截图:

img.png

Spring注入方式 {#Spring注入方式}

Spring注入有 3种方式:

  1. Constructor-based dependency injection 基于构造器注入
  2. Setter-based dependency injection 基于set方法注入
  3. Field-based dependency injection 基于字段注入

基于构造器注入 {#基于构造器注入}

基于构造器注入,顾名思义,就是在类的构造器上加@Autowired注解。这种注入方式的主要优点是可以将注入的字段声明为 final,
final修饰的字段是在运行时被初始化,可以直接赋值,也可以在实例构造器中赋值,赋值后不可修改。下文给出了一个基于构造器注入的例子:

|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | @Component public class ConstructorBasedInjection { private final Object object; // @Autowired注解可以省去 @Autowired public ConstructorBasedInjection(Object object) { this.object = object; } } |

基于setter方法注入 {#基于setter方法注入}

基于set方法注入,即在 setter方法上加 @Autowired注解,当使用无参数构造函数或无参数静态工厂方法实例化 Bean时,Spring容器会调用这些 setter方法,以便注入 Bean的依赖项,下面给出了一个基于 setter方法注入的例子:

|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 | @Component public class SetterBasedInjection { private final Object object; @Autowired public Object setObject(Object object) { this.object = object; } public Object getObject() { return object; } } |

基于字段注入 {#基于字段注入}

基于字段注入,就是在字段上加 @Autowired注解,在构造 bean之后,调用任何配置方法之前,Spring容器会立即注入这些字段,以下是一个基于字段注入的例子:

|-------------------|-------------------------------------------------------------------------------------------------| | 1 2 3 4 5 | @Component public class FiledBasedInjection { @Autowired private final Object object; } |

从上面 3种注入方式,我们可以看出:基于字段注入是最简洁的注入方式,因为它避免了添加繁冗的 getter和 setter样板代码,并且无需为类声明构造函数。但是正如 idea编译器警告的一样,基于字段注入方式势必存在某些缺点,以至于它的创造者 Spring官方不推荐使用它,下文就给出几个基于字段注入可能的缺点。

基于字段的依赖注入的缺点 {#基于字段的依赖注入的缺点}

容易引发NPE {#容易引发NPE}

因为Spring IOC容器在使用字段依赖注入时,并不会对依赖的bean是否为 null做判断,因此在下面的代码中,通过 @Autowired 注入的user对象可能为空,而JVM 虚拟机在编译时也无法检测出 user为 null,只有在运行时调用 user的方法时,发现 user为 null,出现空指针异常(NPE)。

|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 | @Component public class FiledBasedInjection { private String name; @Autowired private final User user; public FiledBasedInjection(){ this.name = user.getName(); // NPE } } |

缓解单一职责原则的违反 {#缓解单一职责原则的违反}

使用基于字段的注解,我们无需关注类之间的依赖关系,完全依赖于Spring IOC容器的管理,但是使用"基于构造器注入的方式",我们需要手动在类代码中去编写需要依赖的类,当依赖的类越来越多,我们就能发现 code smell,这个时候就能显示的提醒我们,代码的质量是否有问题。

因此,尽管字段注入不直接负责打破单一责任原则,但它通过隐藏了和构造器注入一样发现 code smell的机会,示例代码如下:

|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Component public class ConstructorBasedInjection { private final Object object; private final Object object2; ... private final Object objectX; // @Autowired注解可以省去 @Autowired public ConstructorBasedInjection(Object object, Object object2, ... , Object objectX) { this.object = object; this.object2 = object2; ... this.objectX = objectX; } } |

Spring官方推荐的注入方式 {#Spring官方推荐的注入方式}

Spring官方推荐使用基于构造器注入的方式。

另外,在国内 dubbo,RocketMQ等很多开源框架的源码都已经转向了基于构造器的注入方式,所以开发中我们应该尊重 Spring官方的推荐,尽管其他的方式可以解决,但是不推荐。

不过基于构造器注入有一个潜在的问题就是循环依赖,如下代码,ClassA初始化时依赖 ClassB,因此需要去初始化ClassB。ClassB初始化时又需要依赖 ClassA,进而转向初始化ClassA。所以就形成了经典的"循坏依赖问题",如下代码:

|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Component public class ClassA { private final ClassB classB; @Lazy public ClassA(ClassB classB){ this.classB = classB; } } @Component public class ClassB { private final ClassA classA; public ClassB(ClassA classA){ this.classA = classA; } } |

关于 Spring 如何解决构造器注入的循环依赖,欢迎查看 Spring如何解决构造器注入的循环依赖?

总结 {#总结}

  • Spring注入的方式有:基于字段注入,基于setter方法注入,基于构造器注入 3种方式。

  • Spring官方不推荐 @Autowire使用在基于字段注入方式,推荐基于构造器注入,主要原因是:字段依赖注入容易引发 NPE 空指针异常,而构造器注入时会进行校验,若果依赖的 bean找不到就会抛出 NoSuchBeanDefinitionException,具体代码可以参考源码 org.Springframework.beans.factory.support.ConstructorResolver#resolveAutowiredArgument 实现。

赞(2)
未经允许不得转载:工具盒子 » 为什么不推荐 @Autowired用于字段注入?