Spring依赖注入的几种方式详解

在Spring框架中,依赖注入(Dependency Injection,简称DI)是控制反转(Inversion of Control,简称IoC)的核心实现方式。它通过将对象的依赖关系由自身创建改为外部容器注入,降低了组件间的耦合度,提高了代码的可维护性和可测试性。本文将详细介绍Spring中常用的几种依赖注入方式,包括构造器注入、Setter方法注入、字段注入、接口注入,并分析每种方式的特点与适用场景。

一、构造器注入(Constructor Injection)

构造器注入是通过调用对象的构造方法,将依赖的对象作为参数传入并完成初始化的注入方式。Spring容器在创建被注入对象时,会先创建其依赖的对象,再通过构造方法注入依赖。

1. 实现步骤

(1)定义依赖接口及实现类

// 依赖接口
public interface UserService {
    void queryUser();
}

// 依赖实现类
public class UserServiceImpl implements UserService {
    @Override
    public void queryUser() {
        System.out.println("查询用户信息");
    }
}

(2)在目标类中定义含依赖参数的构造方法

public class UserController {
    // 依赖的对象
    private UserService userService;

    // 构造器注入:通过构造方法接收依赖
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void getUser() {
        userService.queryUser();
    }
}

(3)XML配置文件中配置Bean及依赖关系(或使用注解)

方式一:XML配置

<!-- 配置依赖Bean -->
<bean id="userService" class="com.example.service.UserServiceImpl"/>

<!-- 配置目标Bean,通过constructor-arg注入依赖 -->
<bean id="userController" class="com.example.controller.UserController">
    <constructor-arg ref="userService"/>
</bean>

方式二:注解配置(@Component + @Autowired)

// 依赖类注解
@Component
public class UserServiceImpl implements UserService { ... }

// 目标类注解,构造方法上添加@Autowired(Spring4.3+可省略)
@Component
public class UserController {
    private UserService userService;

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

2. 优缺点

优点:

  • 强制依赖:通过构造方法参数明确依赖关系,确保对象创建时依赖已完全初始化,避免空指针异常。
  • 不可变:依赖对象可声明为final,一旦注入无法修改,提高对象的安全性。
  • 符合面向对象设计:构造方法是对象初始化的天然方式,明确了对象的创建条件。

缺点:

  • 依赖过多时,构造方法参数会变得冗长,影响代码可读性。
  • 循环依赖问题:若两个对象相互通过构造器注入依赖,会导致Spring容器初始化失败(Spring无法解决构造器循环依赖)。

3. 适用场景

适用于依赖关系稳定、必填的场景,例如核心业务组件的依赖注入。

二、Setter方法注入(Setter Injection)

Setter方法注入是通过调用对象的Setter方法,将依赖的对象注入到目标对象中。Spring容器先创建目标对象(通过无参构造方法),再创建依赖对象,最后调用Setter方法完成依赖注入。

1. 实现步骤

(1)定义依赖接口及实现类(同构造器注入)

(2)在目标类中定义依赖属性,并提供对应的Setter方法

public class UserController {
    private UserService userService;

    // Setter方法,用于注入依赖
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void getUser() {
        userService.queryUser();
    }
}

(3)配置Bean及依赖关系

方式一:XML配置

<bean id="userService" class="com.example.service.UserServiceImpl"/>
<bean id="userController" class="com.example.controller.UserController">
    <property name="userService" ref="userService"/>
</bean>

方式二:注解配置(@Autowired + Setter方法)

@Component
public class UserController {
    private UserService userService;

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

2. 优缺点

优点:

  • 可选依赖:允许依赖为可选,可通过默认值避免空指针,灵活性更高。
  • 解决循环依赖:Spring通过三级缓存机制,可解决Setter方法注入的循环依赖问题。
  • 代码简洁:当依赖较多时,相比构造器注入,Setter方法更简洁,可读性更好。

缺点:

  • 对象初始化不完整:对象创建时依赖未注入,需调用Setter方法后才完成初始化,可能存在对象状态不一致的风险。
  • 依赖可修改:Setter方法可被多次调用,可能导致依赖对象被篡改。

3. 适用场景

适用于依赖为可选、动态变化的场景,例如配置类的属性注入。

三、字段注入(Field Injection)

字段注入是通过直接在目标类的成员变量上添加注解(如@Autowired、@Resource),由Spring容器直接将依赖对象注入到字段中,无需构造方法或Setter方法。

1. 实现步骤

@Component
public class UserController {
    // 直接在字段上添加@Autowired注解完成注入
    @Autowired
    private UserService userService;

    public void getUser() {
        userService.queryUser();
    }
}

说明:除了@Autowired,还可使用JDK自带的@Resource注解进行字段注入,两者的区别在于:@Autowired按类型注入,@Resource默认按名称注入,可指定name属性;@Autowired是Spring注解,@Resource是JSR-250规范注解,通用性更强。

2. 优缺点

优点:

  • 代码最简洁:无需编写构造方法和Setter方法,减少模板代码。
  • 使用便捷:直接在字段上添加注解即可完成注入,上手成本低。

缺点:

  • 破坏封装性:直接访问类的成员变量,违反面向对象的封装原则。
  • 难以测试:无法通过构造方法或Setter方法传入模拟依赖,单元测试时需依赖Spring容器,测试成本高。
  • 无法注入final字段:final字段必须在构造方法中初始化,无法通过字段注入赋值。
  • 循环依赖风险:若不当使用,可能导致循环依赖问题难以排查。

3. 适用场景

适用于简单的Demo项目或非核心业务组件,不推荐在复杂的生产环境中使用。

四、接口注入(Interface Injection)

接口注入是通过让目标类实现特定的注入接口,由Spring容器通过接口方法将依赖注入到目标对象中。这种方式是早期Spring支持的注入方式,目前已基本淘汰,官方不推荐使用。

1. 实现步骤

(1)定义注入接口

public interface ServiceInjector {
    void injectUserService(UserService userService);
}

(2)目标类实现注入接口

public class UserController implements ServiceInjector {
    private UserService userService;

    @Override
    public void injectUserService(UserService userService) {
        this.userService = userService;
    }

    public void getUser() {
        userService.queryUser();
    }
}

(3)Spring容器通过接口方法注入依赖

<bean id="userService" class="com.example.service.UserServiceImpl"/>
<bean id="userController" class="com.example.controller.UserController">
    <property name="userService" ref="userService"/>
</bean>

2. 优缺点

优点:实现简单,早期用于规范注入方式。

缺点:

  • 侵入性强:目标类必须实现特定接口,耦合度高,违反“开闭原则”。
  • 灵活性差:接口定义固定,无法适配不同的依赖注入需求。

3. 适用场景

基本不推荐使用,仅用于兼容极早期的遗留项目。

五、几种注入方式的对比与选择建议

注入方式优点缺点适用场景
构造器注入强制依赖、不可变、无循环依赖(构造器循环依赖无法解决)、符合OOP依赖过多时参数冗长核心业务、必填依赖
Setter方法注入可选依赖、解决循环依赖、代码简洁对象初始化不完整、依赖可修改可选依赖、动态配置
字段注入代码最简洁、使用便捷破坏封装、测试困难、无法注入final字段简单Demo、非核心组件
接口注入实现简单侵入性强、灵活性差遗留项目兼容

选择建议:

  1. 优先使用构造器注入:对于核心业务组件的必填依赖,构造器注入能保证对象初始化的完整性和安全性,是Spring官方推荐的注入方式。
  2. 次选Setter方法注入:对于可选依赖或需要动态修改的依赖,Setter方法注入更灵活,且能解决循环依赖问题。
  3. 尽量避免字段注入:虽然代码简洁,但破坏封装性,增加测试难度,仅在简单场景下临时使用。
  4. 杜绝接口注入:侵入性强,已被淘汰,不适合新项目开发。

六、总结

Spring依赖注入是实现IoC的核心手段,不同的注入方式各有优劣,适用于不同的业务场景。在实际开发中,应根据依赖的类型(必填/可选)、业务的稳定性要求等因素选择合适的注入方式。核心原则是:优先保证代码的健壮性和可维护性,减少组件间的耦合度。通过合理使用依赖注入,能让Spring应用的架构更清晰、更易扩展。


作 者:南烛
链 接:https://www.itnotes.top/archives/1305
来 源:IT笔记
文章版权归作者所有,转载请注明出处!


上一篇
下一篇