依赖注入(DI)
定位说明
Spring 系列第 2 篇。核心回答:容器怎么把依赖"塞"进对象——构造器/Setter/字段三种注入方式对比、依赖解析策略、循环依赖三级缓存(面试重灾区)、DI 核心流程伪代码。读完 IOC 篇再读本篇。
第一部分:问题的提出
一、传统方式的困境
想象一个场景:
// 传统方式:手动创建和管理依赖
public class OrderService {
private OrderRepository orderRepository;
private PaymentService paymentService;
private NotificationService notificationService;
public OrderService() {
// 需要手动创建所有依赖
this.orderRepository = new OrderRepository();
this.paymentService = new PaymentService();
this.notificationService = new NotificationService();
}
public void createOrder(Order order) {
// 业务逻辑
orderRepository.save(order);
paymentService.processPayment(order);
notificationService.sendNotification(order);
}
}
// 使用时也要手动创建
OrderService orderService = new OrderService();
orderService.createOrder(order);
这种方式有什么问题?
- 强耦合 - 直接依赖具体实现类,修改实现需要改动代码
- 难以测试 - 单元测试无法mock依赖
- 难以扩展 - 要替换实现类需要修改构造函数
- 代码冗长 - 重复的对象创建代码
- 依赖链复杂 - 如果OrderRepository还有其他依赖,会形成复杂的依赖链
OrderService
├─ OrderRepository
│ ├─ Database
│ └─ Logger
├─ PaymentService
│ ├─ PaymentGateway
│ └─ Logger
└─ NotificationService
├─ EmailService
└─ Logger
二、理想的解决方案
我们希望能这样写:
// 理想方式:只声明依赖,框架自动注入
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Autowired
private NotificationService notificationService;
public void createOrder(Order order) {
// 只关注业务逻辑
orderRepository.save(order);
paymentService.processPayment(order);
notificationService.sendNotification(order);
}
}
// 使用时直接注入,无需创建
@Autowired
private OrderService orderService;
orderService.createOrder(order); // 直接使用
这就是依赖注入要解决的问题:让框架自动管理对象的依赖关系。
第二部分:DI的核心概念
三、什么是依赖注入?
3.1 DI的定义
依赖注入(Dependency Injection, DI)是一种设计模式,通过外部容器动态地将对象所依赖的其他对象注入到组件中,从而解耦组件间的依赖关系。
┌──────────────────────────────────────────┐
│ DI的本质 │
├──────────────────────────────────────────┤
│ │
│ 传统方式: │
│ ├─ 对象自己创建依赖 │
│ ├─ 对象自己管理依赖 │
│ └─ 高度耦合 │
│ │
│ DI方式: │
│ ├─ 容器创建依赖 │
│ ├─ 容器注入依赖 │
│ └─ 解耦 │
│ │
│ 核心思想: │
│ └─ "我需要什么,不是我自己去找, │
│ 而是别人(容器)送给我" │
│ │
└──────────────────────────────────────────┘
3.2 DI与IOC的关系
┌──────────────────────────────────────────┐
│ DI与IOC的关系 │
├──────────────────────────────────────────┤
│ │
│ IOC(控制反转) │
│ ├─ 是一种设计思想 │
│ ├─ 强调控制权的反转 │
│ └─ 目标:解耦、灵活、可维护 │
│ │
│ DI(依赖注入) │
│ ├─ 是实现IOC的一种方式 │
│ ├─ 强调依赖的注入 │
│ └─ 手段:通过容器注入依赖 │
│ │
│ 关系: │
│ └─ IOC是目标,DI是手段 │
│ IOC ⊃ DI │
│ │
└──────────────────────────────────────────┘
3.3 DI的核心优势
┌──────────────────────────────────────────┐
│ DI的核心优势 │
├──────────────────────────────────────────┤
│ │
│ 1. 解耦 │
│ ├─ 依赖接口而不是实现 │
│ ├─ 实现类可以随意替换 │
│ └─ 修改不会影响调用方 │
│ │
│ 2. 灵活性 │
│ ├─ 通过配置选择不同实现 │
│ ├─ 支持条件装配 │
│ └─ 支持动态代理和增强 │
│ │
│ 3. 可测试性 │
│ ├─ 容易mock依赖 │
│ ├─ 支持单元测试 │
│ └─ 支持集成测试 │
│ │
│ 4. 自动化 │
│ ├─ 自动创建依赖 │
│ ├─ 自动注入依赖 │
│ └─ 自动管理生命周期 │
│ │
│ 5. 可维护性 │
│ ├─ 配置集中管理 │
│ ├─ 易于扩展 │
│ └─ 代码更清晰 │
│ │
└──────────────────────────────────────────┘
第三部分:DI的三种注入方式
四、构造器注入(Constructor Injection)
4.1 基本用法
// 构造器注入:通过构造函数注入依赖
@Service
public class UserService {
private final UserRepository userRepository;
private final UserValidator userValidator;
// Spring 4.3+ 单参构造器默认@Autowired,可省略
@Autowired
public UserService(UserRepository userRepository, UserValidator userValidator) {
this.userRepository = userRepository;
this.userValidator = userValidator;
}
public void createUser(User user) {
userValidator.validate(user);
userRepository.save(user);
}
}
// 使用时
@Autowired
private UserService userService;
userService.createUser(user);
4.2 构造器注入的特点
┌──────────────────────────────────────────┐
│ 构造器注入的特点 │
├──────────────────────────────────────────┤
│ │
│ 优点 │
│ ├─ 强制依赖:依赖缺失会编译错误 │
│ ├─ 不可变:final字段,线程安全 │
│ ├─ 避免循环依赖:Spring启动时检测 │
│ ├─ 易于测试:可直接传入mock对象 │
│ └─ 清晰表达:依赖关系一目了然 │
│ │
│ 缺点 │
│ ├─ 参数过多时不美观 │
│ ├─ 无法注入可选依赖 │
│ └─ 无法注入循环依赖 │
│ │
│ 推荐场景 │
│ ├─ 核心业务组件 │
│ ├─ 必须依赖 │
│ └─ 需要保证不可变性 │
│ │
└──────────────────────────────────────────┘
4.3 多参数构造器注入
// 当依赖过多时,可以使用Lombok简化
@Service
@RequiredArgsConstructor // Lombok自动生成构造器
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
private final UserService userService;
public void createOrder(Order order) {
// 业务逻辑
}
}
// 等价于
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
private final UserService userService;
public OrderService(OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService,
UserService userService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
this.userService = userService;
}
}
4.4 单元测试中的构造器注入
// 构造器注入便于单元测试
public class UserServiceTest {
private UserService userService;
private UserRepository mockRepository;
private UserValidator mockValidator;
@Before
public void setUp() {
// 创建mock对象
mockRepository = mock(UserRepository.class);
mockValidator = mock(UserValidator.class);
// 通过构造器注入mock对象
userService = new UserService(mockRepository, mockValidator);
}
@Test
public void testCreateUser() {
User user = new User("Tom", 18);
// 设置mock行为
when(mockValidator.validate(user)).thenReturn(true);
// 执行测试
userService.createUser(user);
// 验证调用
verify(mockRepository).save(user);
}
}
五、Setter注入(Setter Injection)
5.1 基本用法
// Setter注入:通过setter方法注入依赖
@Service
public class UserService {
private UserRepository userRepository;
private UserValidator userValidator;
// 可选依赖
@Autowired(required = false)
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired(required = false)
public void setUserValidator(UserValidator userValidator) {
this.userValidator = userValidator;
}
public void createUser(User user) {
if (userValidator != null) {
userValidator.validate(user);
}
if (userRepository != null) {
userRepository.save(user);
}
}
}
5.2 Setter注入的特点
┌──────────────────────────────────────────┐
│ Setter注入的特点 │
├──────────────────────────────────────────┤
│ │
│ 优点 │
│ ├─ 灵活:支持可选依赖 │
│ ├─ 兼容:对第三方类兼容性好 │
│ ├─ 可变:可以在运行时修改依赖 │
│ └─ 简洁:代码相对简洁 │
│ │
│ 缺点 │
│ ├─ 不强约束:容器可能注入不完整 │
│ ├─ 可变性:依赖可被修改,线程不安全 │
│ ├─ 隐藏依赖:不清楚需要什么依赖 │
│ └─ 难以测试:需要手动调用setter │
│ │
│ 推荐场景 │
│ ├─ 可选依赖 │
│ ├─ 配置注入 │
│ └─ 旧代码维护 │
│ │
└──────────────────────────────────────────┘
六、字段注入(Field Injection)
6.1 基本用法
// 字段注入:通过@Autowired注解注入依赖
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserValidator userValidator;
public void createUser(User user) {
userValidator.validate(user);
userRepository.save(user);
}
}
6.2 字段注入的特点
┌──────────────────────────────────────────┐
│ 字段注入的特点 │
├──────────────────────────────────────────┤
│ │
│ 优点 │
│ ├─ 简洁:写法最简洁 │
│ ├─ 广泛:最常见的注入方式 │
│ └─ 快速:快速开发时方便 │
│ │
│ 缺点 │
│ ├─ 难以测试:不好mock,需要反射注入 │
│ ├─ 隐藏依赖:不清楚需要什么依赖 │
│ ├─ 可变性:依赖可被修改,线程不安全 │
│ ├─ 违背原则:违背了构造器注入的原则 │
│ └─ 不支持final:无法使用final字段 │
│ │
│ 推荐场景 │
│ ├─ 普通业务类 │
│ ├─ 快速开发 │
│ └─ 非核心组件 │
│ │
└──────────────────────────────────────────┘
6.3 字段注入的单元测试困难
// 字段注入的单元测试困难
public class UserServiceTest {
private UserService userService;
@Before
public void setUp() {
userService = new UserService();
// ❌ 无法通过构造器注入mock对象
// 需要通过反射手动注入
UserRepository mockRepository = mock(UserRepository.class);
UserValidator mockValidator = mock(UserValidator.class);
// 通过反射注入(复杂且不优雅)
try {
Field repositoryField = UserService.class.getDeclaredField("userRepository");
repositoryField.setAccessible(true);
repositoryField.set(userService, mockRepository);
Field validatorField = UserService.class.getDeclaredField("userValidator");
validatorField.setAccessible(true);
validatorField.set(userService, mockValidator);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Test
public void testCreateUser() {
// 测试代码
}
}
// ✅ 使用构造器注入则简单得多
public class UserServiceTest {
private UserService userService;
private UserRepository mockRepository;
private UserValidator mockValidator;
@Before
public void setUp() {
mockRepository = mock(UserRepository.class);
mockValidator = mock(UserValidator.class);
userService = new UserService(mockRepository, mockValidator);
}
}
七、三种注入方式对比
7.1 详细对比表
┌──────────────────────────────────────────────────────────────┐
│ 三种注入方式详细对比 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 维度 构造器注入 Setter注入 字段注入 │
│ ───────────────────────────────────────────────────────── │
│ 依赖清晰度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐ │
│ 不可变性 ⭐⭐⭐⭐⭐ ⭐ ⭐ │
│ 测试友好度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐ │
│ 灵活性 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ │
│ 可选依赖 ❌ ✅ ✅ │
│ 循环依赖 ✅ ✅ ❌ │
│ 代码简洁度 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ │
│ 推荐度 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐ │
│ │
└──────────────────────────────────────────────────────────────┘
7.2 选择建议
// 1. 优先使用构造器注入
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public OrderService(OrderRepository orderRepository, PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
}
// 2. 可选依赖使用Setter注入
@Service
public class UserService {
private UserRepository userRepository;
private UserCache userCache; // 可选
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired(required = false)
public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}
}
// 3. 快速开发时可使用字段注入
@Service
public class CustomerService {
@Autowired
private NotificationService notificationService;
}
// 4. 避免混用,保持一致性
// ❌ 不要混用多种注入方式
@Service
public class BadService {
@Autowired
private UserRepository userRepository; // 字段注入
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) { // Setter注入
this.paymentService = paymentService;
}
}
// ✅ 应该统一使用一种方式
@Service
public class GoodService {
private final UserRepository userRepository;
private final PaymentService paymentService;
public GoodService(UserRepository userRepository, PaymentService paymentService) {
this.userRepository = userRepository;
this.paymentService = paymentService;
}
}
第四部分:依赖解析策略
八、按类型匹配(ByType)
8.1 基本用法
// 按类型匹配:默认策略
// 1. 定义接口
public interface UserRepository {
User findById(int id);
}
// 2. 定义实现
@Repository
public class MysqlUserRepository implements UserRepository {
@Override
public User findById(int id) {
return new User(id, "Tom");
}
}
// 3. 注入时按类型匹配
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 按UserRepository类型查找
public User getUser(int id) {
return userRepository.findById(id);
}
}
// Spring会自动找到MysqlUserRepository的实现并注入
8.2 多实现的处理
// 当有多个实现时,需要指定具体实现
// 1. 定义接口
public interface PaymentGateway {
void processPayment(Order order);
}
// 2. 定义多个实现
@Component("alipayGateway")
public class AlipayGateway implements PaymentGateway {
@Override
public void processPayment(Order order) {
System.out.println("支付宝支付");
}
}
@Component("wechatGateway")
public class WechatGateway implements PaymentGateway {
@Override
public void processPayment(Order order) {
System.out.println("微信支付");
}
}
// 3. 使用@Qualifier指定具体实现
@Service
public class OrderService {
@Autowired
@Qualifier("alipayGateway")
private PaymentGateway paymentGateway;
public void createOrder(Order order) {
paymentGateway.processPayment(order);
}
}
九、按名称匹配(ByName)
9.1 基本用法
// 按名称匹配:通过@Qualifier或@Resource
// 1. 定义Bean
@Component("mysqlRepository")
public class MysqlUserRepository implements UserRepository {
}
@Component("mongoRepository")
public class MongoUserRepository implements UserRepository {
}
// 2. 按名称注入
@Service
public class UserService {
// 方式1:@Autowired + @Qualifier
@Autowired
@Qualifier("mysqlRepository")
private UserRepository userRepository;
// 方式2:@Resource(name = "...")
@Resource(name = "mysqlRepository")
private UserRepository userRepository2;
}
9.2 @Autowired vs @Resource
┌──────────────────────────────────────────┐
│ @Autowired vs @Resource │
├──────────────────────────────────────────┤
│ │
│ 维度 @Autowired @Resource │
│ ───────────────────────────────────── │
│ 来源 Spring Java标准 │
│ 默认匹配方式 ByType ByName │
│ 支持@Qualifier ✅ ❌ │
│ required属性 ✅ ❌ │
│ 可移植性 ❌ ✅ │
│ IDE推荐 ❌ ✅ │
│ │
└──────────────────────────────────────────┘
9.3 使用建议
// 1. 优先使用@Autowired + @Qualifier
@Service
public class UserService {
@Autowired
@Qualifier("mysqlRepository")
private UserRepository userRepository;
}
// 2. 需要按名称匹配时使用@Resource
@Service
public class UserService {
@Resource(name = "mysqlRepository")
private UserRepository userRepository;
}
// 3. 可选依赖使用@Autowired(required = false)
@Service
public class UserService {
@Autowired(required = false)
private UserCache userCache;
}
// 4. 避免使用@Resource的name属性
// ❌ 不推荐
@Resource(name = "userRepository")
private UserRepository userRepository;
// ✅ 推荐
@Autowired
@Qualifier("userRepository")
private UserRepository userRepository;
十、集合注入
10.1 List注入
// 注入所有实现
// 1. 定义接口
public interface NotificationHandler {
void handle(Notification notification);
}
// 2. 定义多个实现
@Component
public class EmailNotificationHandler implements NotificationHandler {
@Override
public void handle(Notification notification) {
System.out.println("发送邮件");
}
}
@Component
public class SmsNotificationHandler implements NotificationHandler {
@Override
public void handle(Notification notification) {
System.out.println("发送短信");
}
}
@Component
public class PushNotificationHandler implements NotificationHandler {
@Override
public void handle(Notification notification) {
System.out.println("发送推送");
}
}
// 3. 注入所有实现
@Service
public class NotificationService {
@Autowired
private List<NotificationHandler> handlers;
public void notify(Notification notification) {
// 遍历所有处理器
for (NotificationHandler handler : handlers) {
handler.handle(notification);
}
}
}
// 4. 控制顺序
@Component
@Order(1)
public class EmailNotificationHandler implements NotificationHandler {
}
@Component
@Order(2)
public class SmsNotificationHandler implements NotificationHandler {
}
@Component
@Order(3)
public class PushNotificationHandler implements NotificationHandler {
}
// 注入时会按@Order排序
10.2 Map注入
// 注入所有实现,以Bean名称为key
// 1. 定义接口
public interface PaymentGateway {
void processPayment(Order order);
}
// 2. 定义多个实现
@Component("alipay")
public class AlipayGateway implements PaymentGateway {
@Override
public void processPayment(Order order) {
System.out.println("支付宝支付");
}
}
@Component("wechat")
public class WechatGateway implements PaymentGateway {
@Override
public void processPayment(Order order) {
System.out.println("微信支付");
}
}
// 3. 注入为Map
@Service
public class PaymentService {
@Autowired
private Map<String, PaymentGateway> gateways;
public void processPayment(Order order, String paymentMethod) {
PaymentGateway gateway = gateways.get(paymentMethod);
if (gateway != null) {
gateway.processPayment(order);
}
}
}
// 4. 使用
PaymentService paymentService = applicationContext.getBean(PaymentService.class);
paymentService.processPayment(order, "alipay"); // 支付宝支付
paymentService.processPayment(order, "wechat"); // 微信支付
第五部分:循环依赖处理
十一、什么是循环依赖?
11.1 循环依赖的类型
// 类型1:直接循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // A依赖B
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // B依赖A
}
// 类型2:间接循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // A→B
}
@Service
public class ServiceB {
@Autowired
private ServiceC serviceC; // B→C
}
@Service
public class ServiceC {
@Autowired
private ServiceA serviceA; // C→A
}
// 类型3:自身依赖
@Service
public class ServiceA {
@Autowired
private ServiceA serviceA; // 自己依赖自己
}
11.2 循环依赖的问题
// 循环依赖会导致容器启动失败
// 如果使用构造器注入,会直接报错
@Service
public class ServiceA {
public ServiceA(ServiceB serviceB) { // 构造器注入
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
public ServiceB(ServiceA serviceA) { // 构造器注入
this.serviceA = serviceA;
}
}
// 启动时报错:
// BeanCurrentlyInCreationException: Error creating bean with name 'serviceA'
十二、Spring 三级缓存与单例循环依赖
12.1 三级缓存结构(简化版 DefaultSingletonBeanRegistry)
// Spring 使用三级缓存解决“单例 Bean 的字段注入循环依赖”
public class DefaultSingletonBeanRegistry {
/**
* 一级缓存:singletonObjects
* - 存放完全初始化完毕的单例 Bean(构造 + 依赖注入 + 初始化都完成)
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
/**
* 二级缓存:earlySingletonObjects
* - 存放“早期暴露的 Bean 实例”(已执行构造函数,但还没依赖注入 / 初始化)
* - 用于解决循环依赖时,被别的 Bean 提前引用
*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
/**
* 三级缓存:singletonFactories
* - 存放可以创建“早期 Bean 实例 / 代理对象”的 ObjectFactory
* - 在需要时,通过工厂创建代理,并将结果放入二级缓存
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();
/**
* 获取单例 Bean 的核心流程
*/
public Object getSingleton(String beanName) {
// 1. 先从一级缓存中查找:完整 Bean
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 2. 再从二级缓存中查找:早期暴露的 Bean(半成品)
singletonObject = earlySingletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 3. 最后从三级缓存中查找:Bean 工厂
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过工厂创建 Bean(注意:有可能创建的是代理对象)
singletonObject = singletonFactory.getObject();
// 将创建好的 Bean 放到二级缓存中(早期引用用它)
earlySingletonObjects.put(beanName, singletonObject);
// 同时从三级缓存中移除对应工厂
singletonFactories.remove(beanName);
}
return singletonObject;
}
/**
* 单例 Bean 创建完成后,放入一级缓存
*/
public void addSingleton(String beanName, Object singletonObject) {
singletonObjects.put(beanName, singletonObject);
earlySingletonObjects.remove(beanName);
singletonFactories.remove(beanName);
}
/**
* Bean 构造完成后(还未依赖注入 / 初始化),注册对应的 ObjectFactory
* 用于后续“提前暴露”
*/
public void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
if (!singletonObjects.containsKey(beanName)) {
singletonFactories.put(beanName, singletonFactory);
earlySingletonObjects.remove(beanName);
}
}
}
12.2 字段注入循环依赖的解决流程(ServiceA ↔ ServiceB)
示例场景:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
前提:
- 两个都是
singleton(默认) - 使用的是 字段注入 / setter 注入(不是构造器注入)
流程(简化版):
- 创建 ServiceA
- 调用构造器,实例化
ServiceA - 将
ServiceA对应的 ObjectFactory 放入三级缓存(singletonFactories) - 此时:
- 一级:无
- 二级:无
- 三级:
factory(A)
- 调用构造器,实例化
- 为 ServiceA 注入依赖 → 发现需要 ServiceB
- 容器开始创建
ServiceB - 实例化
ServiceB,调用构造器 - 将
ServiceB对应的 ObjectFactory 放入三级缓存 - 此时可能会把
ServiceB的“早期引用”放入二级缓存(不同版本源码略有差异)
- 容器开始创建
- 为 ServiceB 注入依赖 → 发现需要 ServiceA
getBean("serviceA")- 按顺序查找:
- 一级缓存:没有
- 二级缓存:没有
- 三级缓存:找到了
factory(A)
- 调用
factory(A).getObject()创建 早期的 A(可能是代理):- 将该 A 放入 二级缓存
earlySingletonObjects - 从三级缓存移除 A 的工厂
- 将该 A 放入 二级缓存
- 此时:
- 一级:无
- 二级:
A(半成品) - 三级:
factory(B)
- 把“早期的 A” 注入到 B 中
- 此时
serviceB.serviceA = earlyA - 完成
ServiceB的依赖注入和初始化 - 将
ServiceB放入 一级缓存 - 清理它在二级 / 三级中的痕迹
- 此时
- 回到 ServiceA,继续注入它的依赖
- 现在
ServiceB已经是完整 Bean,在 一级缓存 ServiceA再次获取ServiceB→ 从一级缓存中直接拿- 为
ServiceA完成依赖注入和初始化 - 将
ServiceA放入一级缓存,清理二级缓存中的早期引用
- 现在
- 最终结果
- 一级缓存:
A(完整),B(完整) - 二级 / 三级缓存:不再存放 A/B
- 循环依赖被成功打破
- 一级缓存:
12.3 三个缓存的作用小结
- 一级缓存 singletonObjects
- 完整、可直接使用的单例 Bean
- 绝大部分情况下,从这里直接返回
- 二级缓存 earlySingletonObjects
- 早期暴露的 Bean 实例(已构造,但尚未完成依赖注入 / 初始化)
- 典型用途:被其它单例在循环依赖场景中提前引用
- 三级缓存 singletonFactories
- 存放
ObjectFactory,可以延迟创建 Bean 或代理 - 在需要“提前暴露”某个 Bean 时,通过工厂创建早期引用,并将结果升级到二级缓存
- 存放
12.4 哪些循环依赖可以解决 / 不能解决?
✅ 可以解决的情况
- 单例 + 字段 / setter 注入
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
- 典型的“三级缓存解决循环依赖”的场景。
❌ 无法解决的情况
- 构造器循环依赖
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
- 两个 Bean 在构造阶段就互相需要,此时还没机会把任何一个“早期暴露”出去。
- Spring 会抛出:
BeanCurrentlyInCreationException。
- 原型(prototype)作用域的循环依赖
@Service
@Scope("prototype")
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
@Scope("prototype")
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
- 原型 Bean 每次 getBean 都要重新创建,不会进单例缓存。
- Spring 不会为原型 Bean 构建完整的三级缓存解决链,也就无法用之前的机制打破循环。
- 同样会抛出
BeanCurrentlyInCreationException。
十三、可选依赖:required = false / Optional / ObjectProvider
13.1 @Autowired(required = false)
@Service
public class UserService {
// 必须存在的依赖
@Autowired
private UserRepository userRepository;
// 可选依赖:如果容器中有 UserCache 就注入,没有就保持 null
@Autowired(required = false)
private UserCache userCache;
public User getUser(int id) {
if (userCache != null) {
User cached = userCache.get(id);
if (cached != null) {
return cached;
}
}
User user = userRepository.findById(id);
if (userCache != null) {
userCache.put(id, user);
}
return user;
}
}
13.2 使用 Optional<T>
@Service
public class UserService {
private final UserRepository userRepository;
private final Optional<UserCache> userCache;
public UserService(UserRepository userRepository,
Optional<UserCache> userCache) {
this.userRepository = userRepository;
this.userCache = userCache;
}
public User getUser(int id) {
return userCache
.map(cache -> {
User cached = cache.get(id);
return cached != null ? cached : userRepository.findById(id);
})
.orElseGet(() -> userRepository.findById(id));
}
}
13.3 使用 ObjectProvider<T>
@Service
public class UserService {
private final UserRepository userRepository;
private final ObjectProvider<UserCache> userCacheProvider;
public UserService(UserRepository userRepository,
ObjectProvider<UserCache> userCacheProvider) {
this.userRepository = userRepository;
this.userCacheProvider = userCacheProvider;
}
public User getUser(int id) {
// 延迟获取可选 Bean
UserCache userCache = userCacheProvider.getIfAvailable();
if (userCache != null) {
User cached = userCache.get(id);
if (cached != null) {
return cached;
}
}
return userRepository.findById(id);
}
}
十四、条件注入:@Conditional* 与 @Profile
14.1 @ConditionalOnProperty —— 根据配置决定 Bean
public interface CacheManager {
void put(String key, Object value);
Object get(String key);
}
@Component
@ConditionalOnProperty(name = "cache.type", havingValue = "redis")
public class RedisCacheManager implements CacheManager {
@Override
public void put(String key, Object value) {
System.out.println("Redis 缓存写入");
}
@Override
public Object get(String key) {
return null;
}
}
@Component
@ConditionalOnProperty(name = "cache.type", havingValue = "memory", matchIfMissing = true)
public class MemoryCacheManager implements CacheManager {
@Override
public void put(String key, Object value) {
System.out.println("内存缓存写入");
}
@Override
public Object get(String key) {
return null;
}
}
application.properties 示例:
cache.type=redis
14.2 @ConditionalOnClass —— 类存在才生效
@Component
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public class RedisService {
// 只有引入了 Jedis 依赖,这个 Bean 才会被加载
}
14.3 @ConditionalOnMissingBean —— 容器中没有某 Bean 时才创建
@Configuration
public class RepositoryConfig {
@Bean
@ConditionalOnMissingBean(UserRepository.class)
public UserRepository userRepository() {
// 当容器中没有 UserRepository 的其他实现时,才创建默认实现
return new DefaultUserRepository();
}
}
14.4 @Profile —— 按环境选择 Bean
public interface DataSource {
void connect();
}
@Component
@Profile("dev")
public class DevDataSource implements DataSource {
@Override
public void connect() {
System.out.println("开发环境数据源");
}
}
@Component
@Profile("prod")
public class ProdDataSource implements DataSource {
@Override
public void connect() {
System.out.println("生产环境数据源");
}
}
配置环境:
# application.properties
spring.profiles.active=dev
十五、依赖注入(DI)的核心流程(Spring 大致做了什么)
伪代码 / 思路版,方便你面试时脑中有“完整链路”。
- 扫描配置
- 扫描
@Component,@Service,@Repository,@Controller等,生成BeanDefinition。
- 扫描
- 创建 Bean 实例
- 调用构造函数实例化对象(构造器注入在这个阶段完成)。
- 填充属性(依赖注入)
- 解析字段 / setter 上的
@Autowired/@Resource/@Value等; - 利用三级缓存 / BeanFactory 递归获取依赖 Bean,并注入。
- 解析字段 / setter 上的
- 调用 BeanPostProcessor(前置)
- 如:
AutowiredAnnotationBeanPostProcessor、AOP 相关的AutoProxyCreator等。
- 如:
- 调用初始化方法
@PostConstructInitializingBean.afterPropertiesSet()- 自定义
init-method
- 调用 BeanPostProcessor(后置)
- 对 Bean 做一些代理增强(AOP)、包装等。
- 放入单例池
- 对于单例 Bean,将其放入 一级缓存
singletonObjects,供以后直接复用。
- 对于单例 Bean,将其放入 一级缓存
十六、DI 的推荐实践
- 优先使用构造器注入
- 可以配合 Lombok:
@RequiredArgsConstructor - 好处:依赖是
final,更容易做单元测试,也能避免循环依赖时“悄悄注不进去还不知道”。
- 可以配合 Lombok:
- 减少字段注入
- 字段注入只能靠反射赋值,不利于测试和重构;
- 可以保留少量字段注入给框架用(比如在配置类里)。
- 可选依赖用 Optional / ObjectProvider
- 不推荐满世界
required = false; - 推荐:
- 强业务依赖 → 构造器必选参数
- 可选功能 / 插件式依赖 →
Optional<T>或ObjectProvider<T>。
- 不推荐满世界
- 依赖过多要重构
- 一个类依赖超过 4~5 个 Bean,基本就说明“职责过重”;
- 可以提取中间层 / CommonService 来拆分。
- 不要在 Bean 中自己
new其它 Bean- 这样会绕过 Spring 容器,失去代理 / 事务 / 生命周期管理;
- 正确写法:通过注入依赖来使用。
- 少用 ApplicationContext.getBean
- 偶尔为了兼容老代码 / 动态获取 Bean 可以用;
- 不要当成“全局 ServiceLocator”乱用。
- 构造器里不要做重 IO / 大计算
- 构造器职责:只做“装配依赖 + 校验参数”;
- 重初始化放到
@PostConstruct或显式的init()方法中。
💬 评论