依赖注入(Dependency Injection, DI)中的“依赖”详解

1. 什么是“依赖”?

在编程中,依赖(Dependency) 指的是:
一个对象(A)需要另一个对象(B)才能完成其功能。此时,对象B称为对象A的依赖。
例如:

  • UserService 需要 UserRepository 来操作数据库。

  • OrderController 需要 OrderService 来处理业务逻辑。

核心特点

  • 单向性:依赖关系通常是单向的(A依赖B,但B不依赖A)。

  • 必要性:没有依赖对象,当前对象无法正常工作。


2. 依赖注入(DI)的本质

依赖注入是一种设计模式,通过外部容器(如Spring) 将依赖对象(B)主动传递给需要它的对象(A),而不是由对象A自己创建依赖对象B。

  • 核心目标:解耦对象间的直接依赖关系,提升代码的灵活性和可测试性。


3. “相互依赖的对象”是什么?

当两个对象彼此依赖时,称为循环依赖(Circular Dependency)
例如:

public class ClassA {
    private ClassB classB; // ClassA依赖ClassB
    public ClassA(ClassB classB) { this.classB = classB; }
}

public class ClassB {
    private ClassA classA; // ClassB依赖ClassA
    public ClassB(ClassA classA) { this.classA = classA; }
}

此时,ClassA和ClassB形成循环依赖,导致无法正常创建对象。


4. 循环依赖的解决方案
4.1 Spring的默认支持
  • Setter注入:Spring通过三级缓存机制支持循环依赖(仅限非构造器注入)。

  • 构造函数注入:默认不支持循环依赖,需重构代码或调整设计。

4.2 避免循环依赖的最佳实践
  1. 重构设计

    • 引入第三方类(如ClassC)解耦ClassA和ClassB的直接依赖。

    • 使用接口或抽象类隔离依赖。

  2. 使用延迟注入(@Lazy

    @Component
    public class ClassA {
        private final ClassB classB;
        public ClassA(@Lazy ClassB classB) { 
            this.classB = classB; // 延迟初始化ClassB
        }
    }
  3. 改用Setter注入

    @Component
    public class ClassA {
        private ClassB classB;
        @Autowired
        public void setClassB(ClassB classB) { 
            this.classB = classB; 
        }
    }

5. 依赖注入的核心价值
场景 传统方式 依赖注入
依赖管理 对象自行创建依赖(强耦合) 容器统一管理依赖(解耦)
代码可测试性 难以替换依赖(如Mock对象) 轻松替换依赖(便于单元测试)
对象生命周期 对象自行控制依赖的生命周期 容器统一控制生命周期

6. 代码示例:依赖注入 vs 传统方式
6.1 传统方式(强耦合)
public class UserService {
    private UserRepository userRepository = new UserRepository(); // 直接创建依赖
    public void saveUser(User user) {
        userRepository.save(user);
    }
}

问题

  • UserService 与 UserRepository 强耦合,无法替换实现(如测试时替换为Mock对象)。

6.2 依赖注入方式(解耦)

public class UserService {
    private final UserRepository userRepository;
    
    // 通过构造函数注入依赖
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void saveUser(User user) {
        userRepository.save(user);
    }
}

优势

  • UserRepository 由外部容器注入,可灵活替换(如MySQL实现、Mock实现)。


7. 总结
  • 依赖:一个对象正常工作所必需的其他对象。

  • 相互依赖:循环依赖是设计问题,应通过重构或技术手段(如Setter注入)解决。

  • 依赖注入的本质:将依赖的创建和管理权交给容器,实现解耦和灵活性。


附:依赖注入的核心流程图

graph LR
A[容器] -->|创建并管理| B[Bean A]
A -->|创建并管理| C[Bean B]
B -->|依赖注入| C

你可能感兴趣的:(java,spring,开发语言)