吊打面试官系列:Spring为什么不推荐使用字段依赖注入?

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

在开发过程中,肯定会使用依赖注入。大多数情况下会通过@Autowired注解作用在字段上,从而将Bean注入进来。但是Spring官网并没有推荐使用这种方式。包括我们常用的开发工具Idea,也会有警告提示:

我们一起来探究一下原因。

02 Spring推荐的依赖注入

官方文档摘自Spring Framework 6.2.7

Dependency injection (DI)就是我们常说的依赖注入。官方推荐了两种主要的表现形式:基于构造的依赖注入和基于Setter方法的依赖注入。

2.1 基于构造的依赖注入

基于构造函数的依赖注入是通过容器调用一个带有若干参数的构造函数来实现的,每个参数代表一个依赖项。该注入方式不依赖任何容器或者注解。

java 复制代码
@RestController
public class FooController {

    private final UserService userService;

    public FooController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/foo2")
    public String foo2() {
        userService.test();
        return "success";
    }
}

如代码所示,UserService是通过当前类FooController的构造函数,将UserService对象注入到当前类中。注入之后,方法foo2()就可以使用UserService直接调用方法。

构造函数注入时,被注入的对象必须存在,否则项目启动就会报错。

2.2 基于Setter方法的依赖注入

基于Setter 的依赖注入是通过容器在调用无参构造函数或无参static工厂方法实例化 bean 之后,调用 bean 上的 Setter 方法来实现的。

值得注意的是,Setter方法的注入,必须在实例化调用Setter方法才能生效。否者注入的Bean对象就是空。但是项目启动并不会检查是否为空。

容器中正常使用需要借助@Autowired注解,就会实现自动注入。

java 复制代码
@RestController
public class FooController {

    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/foo2")
    public String foo2() {
        userService.test();
        return "success";
    }
}

被注解修饰后的注入对象必须存在,否则也会报错。

2.3 注入对象为空的处理

上述两种注入方式,如果被注入的Bean的确实为空,该怎么处理,官方也给出了标准的解决方案。

通过@Nullable注解可以解决Bean不存在的问题,。改造代码如下:

java 复制代码
private UserService userService;

@Autowired
public void setUserService(@Nullable UserService userService) {
    this.userService = userService;
}

public FooController(@Nullable UserService userService) {
    this.userService = userService;
}

2.4 循环依赖的处理

循环依赖是Spring中经典的问题,我们先看看什么是循环依赖。有两个对象A、B,相互注入,实例化A时,需要B对象,实例化B时,需要A对象,导致无法继续实例化的现象。

案例演示:以UserServiceWorkService为例。

UserService注入WorkService:

WorkService注入UserService:

启动报错日志:

官方给出的解决方案:

大致意思就是:

Spring兼容了大部分场景,Spring 会尽可能延迟设置属性和解决依赖关系,直到 bean 实际被创建时才进行。如果有问题,可以覆盖这种默认行为,使单例 bean 延迟初始化。

@Lazy就是专门来延迟Bean的初始化的。

解决方案也就很简单:直接在任意一个Bean的构造函数上加@Lazy注解。

上面演示的是构造函数注入,Setter方法存在同样的问题,解决方案也相同。

03 字段注入

基于字段的注入。通常使用的注解有@Autowired@Resource@Value,并将其加在字段上的完成属性的注入。

@Value是基于外部属性的注入,本次不做讨论。

字段注入的表现形式:

3.1 @Autowired

@Autowired注解提供了与自动装配协作对象中所述相同的功能,但具有更细粒度的控制和更广泛的适用性。可以作用在构造器、方法、参数、字段以及注解上。

3.2 @Resource

Spring 也支持通过在字段或 bean 属性的设置方法上使用 JSR - 250 的 @Resource 注解(jakarta.annotation.Resource)来进行依赖注入。@Resource注解带有一个 name 属性。默认情况下,Spring 将该属性值解释为要注入的 bean 的名称。只能作用在被修饰的注解作用在类、接口和枚举上,方法以及字段上。

3.3 区别

相同点:

Spring容器中,两个注解的功能最终的结果是相同的。

java 复制代码
@Autowired
private UserService userService;

@Resource
private WorkService workService;

不同点:

  • 作用域不同:

    @Autowired可以作用在构造器、方法、参数、字段以及注解上。

    @Resource只能作用在方法以及字段上。

  • 注入匹配(byNamebyType)的顺序不同:

    @Autowired在注入的时候,先通过byType匹配,无法在Spring容器中找到时,再通过byName的方式匹配。可以和@Qualifier配合使用。

    @Resource恰好和@Autowired相反,先byName,后byType

  • 支持的来源不同:

    @AutowiredSpring 2.5以后提供的注解,只用用于Spring容器。如果不再使用Spring容器,则该注解失效

    @ResourceJDK官方提供的直接(JSR-250),不依赖于任何容器。

  • Bean的默认要求不同:

    @Autowired默认要求Bean必须存在,否者会报错。可以通过属性required=false处理。

    @Resource默认支持null,如果Bean不存在,就会是null值。

04 Spring不推荐字段注入的原因

按照使用结果来讲,没有任何问题。大部分人也确实是这么用的。

4.1单一职责问题

根据设计原则SOLID来讲,一个类的设计应该满足单一职责,就是一个类只做一件事。当我们注入的Bean越来越多,每一个Bean的功能都不相同,就会赋予当前类很多功能,从而违背单一职责的原则。

基于构造和Setter就会解决这个问题么?当然也是不能的,只不过从代码表现形式来看就会显得的很臃肿,这是一种bad smell,间接的提醒我们要去优化,我们来看下案例:

Setter方法也是同样的感觉。

4.2 封装性的破坏

Java的三大特性:封装、继承和多态。其中的封装就是对一个事物、公用组件或流程的进行封装,方面其他人员使用。封装类的私有属性(private)是无法被直接访问的,只能通过Getter方法提供public方法才能使用。

然而字段的注入,就是在私有属性上通过注解的方式,将Bean对象注入进来。Spring容器就间接的访问了封装类的私有属性了,这就破坏了封装性。

Spring作为一个伟大的框架,规范这我们对Java的使用。

05 小结

规范是规范,使用是使用。就像restfull一样,是一种架构风格。使用者可以选择遵守和不遵守,都不会影响使用。目前用的最多的可能还是基于字段的注入@Autowired,用起来更加简洁。

lombok框架的出现,@RequiredArgsConstructor代替了我们写构造方法。使得构造的注入和字段注入一样简洁,那种bad smell的味道直接隐藏起来了。这种情况下字段的注入和构造的注入就变的没有了区别。

你在开发中用的是哪种注入方式呢,评论区讨论!

相关推荐
涡能增压发动积21 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
云烟成雨TD21 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Wenweno0o21 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
行乾21 小时前
鸿蒙端 IMSDK 架构探索
架构·harmonyos
于慨21 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz21 小时前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
swg32132121 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung21 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald21 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川21 小时前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java