基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Spring 依赖注入有哪几种实现方式(构造器注入、Setter 注入、接口注入)?

知识点图片

在 Spring 框架中,依赖注入(Dependency Injection, DI)主要有 3+1 种实现方式。你提到的构造器注入Setter 注入接口注入是理论上的三种经典方式。

但在 Spring 的实际应用中,接口注入基本被废弃/极少用于业务代码,而字段注入(Field Injection)虽然你没有提到,却是平时开发中最常见的。

下面为你详细解析 Spring 中依赖注入的这几种方式及其优缺点:


1. 构造器注入 (Constructor Injection) —— 🏆 官方最推荐

通过类的构造函数来完成依赖关系的设定。Spring 团队自 Spring 4.x 起强烈推荐这种方式。

  • 实现方式: 将依赖项声明为 final 属性,并通过构造方法传入。如果类只有一个构造方法,Spring 4.3 之后甚至可以省略 @Autowired 注解。
  • 代码示例:
    java
    @Service
    public class UserService {
        private final UserRepository userRepository;
    
        // Spring 4.3+ 只有单个构造器时,@Autowired 可以省略
        @Autowired 
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    (日常开发中,常配合 Lombok 的 @RequiredArgsConstructor 注解来简化代码)
  • 优点:
    1. 保证不可变性: 可以将依赖属性声明为 final,防止被意外修改。
    2. 保证完全初始化: 实例化对象时,必须传入所有参数,避免了出现 NullPointerException(对象创建后即刻可用)。
    3. 方便单元测试: 不需要依赖 Spring 容器,直接 new UserService(mockRepository) 即可测试。
    4. 代码异味探测: 如果构造器参数过多,说明该类违反了单一职责原则(SRP),能及时提醒开发者重构。
  • 缺点: 无法解决循环依赖问题(例如 A 依赖 B,B 依赖 A),启动时会直接抛出 BeanCurrentlyInCreationException(不过这反而促使开发者优化架构)。

2. Setter 注入 (Setter Injection)

通过属性的 Setter 方法来完成依赖的注入。

  • 实现方式: 提供无参构造器(隐式或显式),并在 Setter 方法上添加 @Autowired 注解。
  • 代码示例:
    java
    @Service
    public class UserService {
        private UserRepository userRepository;
    
        @Autowired
        public void setUserRepository(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
  • 优点:
    1. 支持可选依赖: 可以配置 @Autowired(required = false),如果容器中没有对应的 Bean 也不报错。
    2. 支持解决循环依赖: Spring 可以先通过无参构造器实例化对象(提前暴露引用),然后再通过 Setter 方法注入依赖。
    3. 灵活性高: 可以在对象运行期间重新注入/修改依赖。
  • 缺点:
    1. 不能将属性声明为 final
    2. 对象可能处于不完整状态:如果忘了调用 Setter 方法(比如在非 Spring 环境下手动 new 对象),直接使用属性会导致空指针异常。

3. 字段注入 (Field Injection) —— ⚠️ 最常见但官方不推荐

直接在类的私有属性上使用 @Autowired@Resource 注解。这是前几年最流行的做法。

  • 实现方式: 依赖 Spring 的反射机制直接给字段赋值。
  • 代码示例:
    java
    @Service
    public class UserService {
        @Autowired
        private UserRepository userRepository;
    }
  • 优点: 代码极其简洁,写起来最快,没有多余的模板代码。
  • 缺点(为何官方和 IDEA 都警告不推荐):
    1. 违反封装原则: 破坏了类的封装性,外部无法通过常规手段(构造器/Setter)传入依赖。
    2. 难以单元测试: 脱离了 Spring 容器,你无法简单地 new 出来并给私有属性赋值,必须使用反射或强依赖 Mockito 等测试框架。
    3. 容易掩盖代码缺陷: 因为加个 @Autowired 太容易了,很容易在一个类里注入十几个依赖,导致类变得极其臃肿,违背了单一职责原则。
    4. 不能使用 final 修饰符。

4. 接口注入 (Interface Injection) —— ❌ Spring 业务开发基本不用

接口注入是指:定义一个接口,里面包含注入依赖的方法,然后让需要依赖的类实现该接口。容器通过调用该接口的方法来注入依赖。

  • 在 Spring 中的地位: Spring 不支持/不使用这种方式来注入业务对象的依赖。这种方式侵入性极强,违背了 Spring "非侵入式(POJO)" 的设计理念。
  • Spring 的特例(Aware 接口):
    虽然不用接口注入业务 Bean,但 Spring 框架内部提供了一系列 Aware 接口(如 ApplicationContextAwareBeanFactoryAware)。如果你的业务类需要获取 Spring 容器底层的对象,可以通过实现这些接口,让 Spring 把自己注入进来。这可以看作是接口注入的一种变体。
  • 代码示例(获取 ApplicationContext):
    java
    @Service
    public class MyService implements ApplicationContextAware {
        private ApplicationContext context;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.context = applicationContext; // Spring 容器会调用此方法注入
        }
    }

总结与最佳实践建议

注入方式 是否支持 final 是否支持可选依赖 循环依赖支持 IDEA 警告 推荐度
构造器注入 ✅ 是 ❌ 否 ❌ 报错 ⭐⭐⭐⭐⭐ (强烈推荐)
Setter注入 ❌ 否 ✅ 是 ✅ 支持 ⭐⭐⭐ (适合可选依赖)
字段注入 ❌ 否 ❌ 否 ✅ 支持 有警告 ⭐ (非常不推荐,但很普遍)
接口注入 ❌ 否 ❌ 否 - - 不适用于业务依赖

💡 目前的最佳实践(Best Practice):
强制依赖/核心依赖使用「构造器注入」,可选/可变依赖使用「Setter 注入」,彻底抛弃「字段注入」。
(如果嫌构造器注入写起来太长,建议使用 Lombok 的 @RequiredArgsConstructor,既优雅又符合规范)。

00:00
00:00