依赖注入三种方式:构造器/Setter/字段注入深度解析
概述
依赖注入(Dependency Injection,DI)是 Spring Framework 的核心功能之一。Spring 提供了三种主要的依赖注入方式:构造器注入(Constructor Injection)、Setter 注入(Setter Injection) 和 字段注入(Field Injection)。本文将深入解析这三种方式的原理、优劣及最佳实践。
一、三种注入方式详解
1.1 构造器注入(Constructor Injection)
构造器注入通过类的构造器参数来注入依赖,是 Spring 最推荐的注入方式。
@Component
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
@Autowired
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
// 业务方法...
}从 Spring 4.3 开始,如果目标类只有一个构造器,可以省略 @Autowired 注解:
@Component
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Spring 4.3+:单一构造器时 @Autowired 可省略
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}优势
| 优势 | 说明 |
|---|---|
| 不可变性 | 依赖字段可声明为 final,确保创建后不可变 |
| 可测试性 | 无需反射或 Spring 容器即可构造对象,单元测试只需 new |
| 循环依赖检测 | 构造器注入会在 Bean 创建时立即检测循环依赖,抛出 BeanCurrentlyInCreationException |
| 非空保证 | 依赖在构造时就必须提供,不会出现 NPE |
| 职责清晰 | 构造器参数明确表达了一个类需要哪些依赖 |
适用场景
- 强制依赖:类必须拥有的依赖
- 不变依赖:整个生命周期内不会变化的依赖
- 需要高可测试性的场景
1.2 Setter 注入(Setter Injection)
Setter 注入通过调用无参构造器创建 Bean 实例后,再调用 Setter 方法注入依赖。
@Component
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}优势
| 优势 | 说明 |
|---|---|
| 可选依赖 | 依赖可配置为可选,容器不会强制注入 |
| 重新注入 | 允许在 Bean 生命周期内重新注入依赖(需注意线程安全) |
| 解决循环依赖 | 结合 @Lazy 或三级缓存机制可解决部分循环依赖问题 |
适用场景
- 可选依赖:类可以没有该依赖也能正常工作
- 需要重新配置的场景:运行期可能需要修改依赖
1.3 字段注入(Field Injection)
字段注入直接通过反射将依赖注入到字段上,是代码最简洁但最不推荐的方式。
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
}缺点
| 缺点 | 说明 |
|---|---|
| 无法使用 final | 字段注入发生在对象创建之后,final 字段必须在构造器中赋值 |
| 隐藏依赖 | 外部无法通过构造器或方法签名看到依赖,违反了"显式依赖"原则 |
| 测试困难 | 无法直接 new 对象测试,必须依赖 Spring 容器或反射注入 |
| 隐藏循环依赖 | 字段注入延迟了循环依赖的检测时机,可能导致运行时才暴露问题 |
| 与 IoC 容器强耦合 | 类无法脱离 Spring 容器独立使用 |
二、@Autowired、@Resource、@Inject 注解对比
Spring 支持三种主要的依赖注入注解,它们来自不同的规范,行为也有所差异。
2.1 对比总览
| 特性 | @Autowired | @Resource | @Inject |
|---|---|---|---|
| 来源 | Spring 原生注解 | JSR-250(JDK 自带) | JSR-330(javax.inject) |
| 包路径 | org.springframework.beans.factory.annotation.Autowired | javax.annotation.Resource | javax.inject.Inject |
| 匹配策略 | 默认 ByType,找到多个候选则 ByName | 默认 ByName,回退 ByType | 默认 ByType(同 @Autowired) |
| 支持 required | 是(默认 true) | 否 | 否 |
| 支持 @Qualifier | 是 | 否(使用 name 属性) | 是(需配合 @Named) |
| 支持 @Primary | 是 | 是 | 是 |
| 与 Spring 耦合 | 强耦合 | 弱耦合(Java EE 标准) | 弱耦合(JSR 标准) |
| 建议场景 | Spring 项目 | 传统 Java EE 项目 | 跨框架项目 |
2.2 @Autowired 详解
@Autowired 是 Spring 最核心的注入注解,执行流程如下:
- 默认按 类型(ByType) 匹配候选 Bean
- 如果找到唯一匹配,直接注入
- 如果找到多个候选 Bean,按 名称(ByName) 回退匹配(字段名/参数名)
- 如果仍无法确定,抛出
NoUniqueBeanDefinitionException - 如果没找到任何候选 Bean,且
required=true(默认),抛出NoSuchBeanDefinitionException
@Component
public class OrderService {
@Autowired
private PaymentService paymentService; // ByType 匹配 PaymentService 类型
// 多个候选时按字段名回退
@Autowired
private PaymentService alipayService; // 匹配名为 "alipayService" 的 Bean
}配合 @Qualifier 精确指定 Bean 名称:
@Component
public class OrderService {
@Autowired
@Qualifier("wechatPay")
private PaymentService paymentService;
}配合 @Primary 设置首选 Bean:
@Primary
@Component
public class AlipayService implements PaymentService {
// 当有多个 PaymentService 实现时,@Primary 标注的作为首选
}2.3 @Resource 详解
@Resource 来自 JSR-250 规范,匹配策略与 @Autowired 不同:
@Component
public class OrderService {
@Resource
private PaymentService paymentService; // 默认 ByName -> 字段名 "paymentService"
@Resource(name = "alipayService")
private PaymentService alipayService; // 指定名称 "alipayService"
}匹配流程:
- 如果指定了
name属性,按名称匹配 - 如果未指定
name,以字段名作为 Bean 名称匹配 - 如果按名称未找到,回退到按类型匹配
- 如果按类型匹配到多个,抛出异常
注意:
@Resource不支持required=false,如果依赖不存在会直接抛出异常。
2.4 @Inject 详解
@Inject 来自 JSR-330 规范,需要额外引入依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>@Component
public class OrderService {
@Inject
private PaymentService paymentService;
@Inject
@Named("wechatPay") // 对应 @Qualifier
private PaymentService wechatPayService;
@Inject
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}@Inject 的行为与 @Autowired 基本一致,区别在于:
- 没有
required属性 - 需要额外引入依赖包
- 使用
@Named替代@Qualifier
三、required=false 原理及源码分析
3.1 使用示例
@Component
public class UserService {
@Autowired(required = false)
private CacheService cacheService; // 如果容器中没有 CacheService 类型的 Bean,注入 null
// 构造器注入也支持 required=false
@Autowired(required = false)
public UserService(@Nullable CacheService cacheService) {
this.cacheService = cacheService;
}
}3.2 源码级别分析
required=false 的底层实现位于 AutowiredAnnotationBeanPostProcessor 中。核心源码路径:
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessororg.springframework.beans.factory.annotation.InjectionMetadata
关键源码分析:
// AutowiredAnnotationBeanPostProcessor.java
// 处理 @Autowired 的核心方法
@Override
public InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
// 1. 收集类中所有需要注入的元素(字段和方法)
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
// 遍历目标类的所有字段
for (Field field : clazz.getDeclaredFields()) {
// 查找 @Autowired 注解
Autowired ann = findAutowiredAnnotation(field);
if (ann != null) {
// 判断是否为 required=false
if (Modifier.isStatic(field.getModifiers())) {
continue; // 跳过静态字段
}
boolean required = ann.required(); // 读取 required 属性
elements.add(new AutowiredFieldElement(field, required));
}
}
// 类似逻辑处理方法和构造器...
return new InjectionMetadata(clazz, elements);
}注入时的实际行为:
// AutowiredFieldElement.java 内部类
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
try {
// 尝试解析依赖
value = beanFactory.resolveDependency(
new DependencyDescriptor(field, this.required),
beanName, null, null
);
} catch (BeansException ex) {
if (this.required) {
throw ex; // required=true 时抛异常
} else {
// required=false 时:记录日志,不抛异常
if (logger.isTraceEnabled()) {
logger.trace("Optional dependency not found...", ex);
}
return; // 不进行注入,保持字段默认值(null)
}
}
if (value != null) {
// 反射设置字段值
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}核心逻辑总结:
@Autowired(required=false)标记的字段在依赖不存在时不会抛出异常- Spring 在注入时会捕获
BeansException,如果required=false则静默处理 - 如果依赖存在但匹配失败(如类型不匹配),即使
required=false也会抛出异常 - 注入结果为
null,由开发者自行处理 NPE 风险
四、源码分析:AutowiredAnnotationBeanPostProcessor 注入点解析流程
4.1 整体架构
AutowiredAnnotationBeanPostProcessor 是 Spring 容器中处理 @Autowired、@Inject、@Value 注解的核心后处理器。它实现了 InstantiationAwareBeanPostProcessor 接口,在 Bean 实例化后、初始化前介入注入过程。
4.2 执行流程时序
ApplicationContext.refresh()
└─> AbstractApplicationContext.finishBeanFactoryInitialization()
└─> DefaultListableBeanFactory.preInstantiateSingletons()
└─> AbstractBeanFactory.getBean()
└─> AbstractAutowireCapableBeanFactory.doCreateBean()
├─> 1. 实例化 Bean(通过构造器或工厂方法)
├─> 2. populateBean() ← 注入入口
│ └─> InstantiationAwareBeanPostProcessor.postProcessProperties()
│ └─> AutowiredAnnotationBeanPostProcessor.postProcessProperties()
│ ├─> findAutowiringMetadata() -> 解析注入点元数据
│ └─> InjectionMetadata.inject() -> 执行注入
│ ├─> 字段注入:反射 Field.set()
│ └─> 方法注入:反射 Method.invoke()
└─> 3. initializeBean() -> 初始化回调4.3 关键源码解析
步骤一:解析注入元数据
// AutowiredAnnotationBeanPostProcessor.java
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
// 使用 ConcurrentHashMap 缓存已解析的元数据
// 键为 Class,值为 InjectionMetadata
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
// 向上遍历类层次结构,包括父类、接口
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 处理所有字段
ReflectionUtils.doWithLocalFields(targetClass, field -> {
// 查找 @Autowired、@Inject、@Value 注解
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
// 静态字段不注入
if (Modifier.isStatic(field.getModifiers())) {
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
// 处理所有方法(包括 Setter)
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
return;
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass(); // 处理父类
}
while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clazz, elements);
}步骤二:执行依赖解析
// AutowiredFieldElement.java(内部类)
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
// 检查缓存(shortcut 优化)
if (this.cached) {
value = resolvedCachedArgument(beanName, bean);
} else {
// 创建依赖描述符,包含 required 信息
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
// 核心解析调用:委托给 BeanFactory
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
} catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
// 缓存解析结果(如果 value 不为 null)
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName)
&& beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());
}
}
} else {
// required=false 且 value==null 时缓存 null
this.cachedFieldValue = null;
}
this.cached = true;
}
}
}
// 如果解析到值,通过反射注入
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}步骤三:DefaultListableBeanFactory.resolveDependency()
// DefaultListableBeanFactory.java
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
// 处理 Optional 类型
if (Optional.class == descriptor.getDependencyType()) {
return createOptionalDependency(descriptor, requestingBeanName);
}
// 处理 @Lazy 解析
else if (ObjectFactory.class == descriptor.getDependencyType()
|| ObjectProvider.class == descriptor.getDependencyType()) {
return new DependencyObjectProvider(descriptor, requestingBeanName);
}
// 处理普通依赖
else {
Object result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
if (result == null && !descriptor.isRequired()) {
// required=false 时返回 null
return null;
}
return result;
}
}
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
// 1. 通过类型查找候选 Bean
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, descriptor.getDependencyType(), descriptor);
if (matchingBeans.isEmpty()) {
// 没有找到候选 Bean
if (descriptor.isRequired()) {
// required=true:抛出 NoSuchBeanDefinitionException
raiseNoMatchingBeanFound(descriptor.getDependencyType(), descriptor.getResolvableType(), descriptor);
}
return null; // required=false:返回 null
}
// 2. 如果有多个候选,进行消歧
if (matchingBeans.size() > 1) {
// 按 @Primary -> @Priority -> 名称匹配 的顺序消歧
String autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
// 无法确定唯一候选
if (descriptor.isRequired()) {
throw new NoUniqueBeanDefinitionException(descriptor.getDependencyType(), matchingBeans.keySet());
}
return null;
}
return matchingBeans.get(autowiredBeanName);
}
// 3. 唯一候选,直接返回
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
return entry.getValue();
} finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}4.4 注入点解析流程总结
buildAutowiringMetadata() // 解析类结构,收集所有 @Autowired 注入点
↓
populateBean() // Bean 属性填充阶段
↓
postProcessProperties() // 后处理器介入
↓
InjectionMetadata.inject() // 遍历所有注入点执行注入
↓
beanFactory.resolveDependency() // 委托 BeanFactory 解析依赖
↓
doResolveDependency() // 核心:查找 -> 消歧 -> 返回
↓
field.set(bean, value) // 反射注入到目标 Bean五、构造器注入的优势深度分析
5.1 不可变性(Immutability)
构造器注入允许依赖字段声明为 final,这带来了以下好处:
@Component
public class UserService {
// final 字段:一旦赋值不可改变
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}- 线程安全:
final字段在 JMM 中有初始化安全性保证,所有线程看到的值都是初始化完成的 - 防止意外修改:编译器阻止对
final字段的重新赋值 - 设计清晰:明确表达"这个字段是类不可变契约的一部分"
5.2 可测试性(Testability)
构造器注入使单元测试变得极其简单,无需依赖 Spring 容器:
// 单元测试:直接 new,无需 Spring 环境
public class UserServiceTest {
private UserService userService;
private UserRepository mockRepository;
private EmailService mockEmailService;
@Before
public void setUp() {
// 使用 Mock 框架创建模拟对象
mockRepository = Mockito.mock(UserRepository.class);
mockEmailService = Mockito.mock(EmailService.class);
// 直接通过构造器注入 Mock 对象
userService = new UserService(mockRepository, mockEmailService);
}
@Test
public void testCreateUser() {
// 定义 Mock 行为
when(mockRepository.save(any())).thenReturn(new User());
// 执行业务逻辑
userService.createUser(new CreateUserRequest());
// 验证交互
verify(mockRepository).save(any());
}
}对比字段注入的测试方式:
// 字段注入的测试——必须使用 Spring 容器或反射
public class UserServiceTest {
@InjectMocks // Mockito 通过反射注入
private UserService userService;
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this); // 初始化 @InjectMocks
}
}5.3 循环依赖检测
构造器注入在 Bean 创建时就能检测到循环依赖,而字段注入和 Setter 注入要等到运行时才暴露。
@Component
public class AService {
private final BService bService;
// 构造器注入
public AService(BService bService) {
this.bService = bService;
}
}
@Component
public class BService {
private final AService aService;
// 构造器注入
public BService(AService aService) {
this.aService = aService;
}
}启动时 Spring 会立即抛出:
┌─────┐
| aService defined in file [AService.class]
↑ ↓
| bService defined in file [BService.class]
└─────┘
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'aService':
Unsatisfied dependency expressed through constructor parameter 0:
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'bService':
Requested bean is currently in creation: Is there an unresolvable circular reference?原理:Spring 在构造器注入时,必须先创建依赖对象才能完成当前对象的构造。当检测到当前 Bean 已在创建中(通过 singletonsCurrentlyInCreation 集合追踪),即判断为循环依赖并抛出异常。
字段注入的循环依赖问题:
@Component
public class AService {
@Autowired
private BService bService; // 字段注入:对象已创建,只是属性未赋值
}
@Component
public class BService {
@Autowired
private AService aService; // 字段注入
}字段注入利用了 Spring 的三级缓存机制可以"解决"循环依赖:
- Spring 通过无参构造器创建
AService实例(此时属性为 null) - 将早期引用(
earlySingletonReference)放入三级缓存 - 开始填充
AService的属性,发现依赖BService - 创建
BService实例,填充属性时发现依赖AService - 从三级缓存获取
AService的早期引用 - 注入完成
但这种"解决"存在风险——被注入的 AService 还未完成完整初始化,调用的方法可能存在问题。
六、字段注入的缺点深度分析
6.1 无法使用 final 关键字
@Component
public class UserService {
// ❌ 编译错误:final 字段必须初始化
@Autowired
private final UserRepository userRepository;
}字段注入发生在对象创建之后(通过反射),而 final 字段要求在构造器执行完成前赋值。这意味着使用字段注入的对象始终是可变的。
6.2 隐藏依赖
字段注入最大的问题之一就是隐藏了类的依赖关系:
@Component
public class OrderService {
@Autowired
private PaymentService paymentService; // 隐藏依赖
@Autowired
private InventoryService inventoryService; // 隐藏依赖
@Autowired
private NotificationService notificationService; // 隐藏依赖
@Autowired
private ShippingService shippingService; // 隐藏依赖
// 外部只看到默认构造器,看不出需要什么依赖
public OrderService() {
}
}对比构造器注入:
@Component
public class OrderService {
private final PaymentService paymentService;
private final InventoryService inventoryService;
private final NotificationService notificationService;
private final ShippingService shippingService;
// 外部一眼看出需要四个依赖
public OrderService(PaymentService paymentService,
InventoryService inventoryService,
NotificationService notificationService,
ShippingService shippingService) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
this.notificationService = notificationService;
this.shippingService = shippingService;
}
}6.3 与 IoC 容器强耦合
// ❌ 字段注入:无法脱离 Spring 使用
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
public User findUser(Long id) {
return userRepository.findById(id);
}
}
// ✅ 构造器注入:纯 Java 对象,容器无关
@Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUser(Long id) {
return userRepository.findById(id);
}
}对于字段注入的类,在没有 Spring 容器的环境下根本无法使用。而构造器注入的类只是一个普通的 POJO,在任何环境下都可以通过 new 关键字创建。
6.4 隐藏循环依赖
字段注入和 Setter 注入利用 Spring 的三级缓存可以在构造器注入会报错的场景下"成功"创建 Bean,这实际上掩盖了设计问题:
// 字段注入:启动时不报错,运行时可能出问题
@Component
public class AService {
@Autowired
private BService bService;
public void doSomething() {
// 调用 bService 时,bService 可能是代理对象而非完全初始化的对象
bService.process();
}
}七、Spring 官方推荐
7.1 Spring 团队官方建议
Spring 团队在官方文档和多个场合明确推荐构造器注入作为首选方式。
Spring 官方文档原文:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null.
7.2 核心原则
| 原则 | 说明 |
|---|---|
| 强制依赖 → 构造器注入 | 必须存在的依赖使用构造器注入 |
| 可选依赖 → Setter 注入 | 可选的、可配置的依赖使用 Setter 注入 |
| 尽量避免字段注入 | 仅用于简单集成测试或框架内部代码 |
7.3 最佳实践总结
@Component
public class UserService {
// ✅ 强制依赖:构造器注入
private final UserRepository userRepository;
private final EmailService emailService;
// ✅ 可选依赖:Setter 注入 + @Autowired(required=false)
private MetricsCollector metricsCollector;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
@Autowired(required = false)
public void setMetricsCollector(MetricsCollector metricsCollector) {
this.metricsCollector = metricsCollector;
}
}7.4 使用 Lombok 简化构造器注入
@Component
@RequiredArgsConstructor // 为 final 字段生成构造器
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// @RequiredArgsConstructor 自动生成:
// public UserService(UserRepository userRepository, EmailService emailService) {
// this.userRepository = userRepository;
// this.emailService = emailService;
// }
}注意:使用
@RequiredArgsConstructor时需确保类只有一个构造器,否则需显式添加@Autowired。
八、总结
| 维度 | 构造器注入 | Setter 注入 | 字段注入 |
|---|---|---|---|
| 不可变性 | ✅ 支持 final | ❌ 不可 final | ❌ 不可 final |
| 可测试性 | ✅ 直接 new | ✅ 直接 new + setter | ❌ 需反射/容器 |
| 循环依赖检测 | ✅ 启动期检测 | ⚠️ 部分解决 | ⚠️ 隐藏风险 |
| 依赖可见性 | ✅ 构造器签名可见 | ✅ Setter 方法可见 | ❌ 不可见 |
| 代码简洁性 | ❌ 需编写构造器 | ❌ 需编写 Setter | ✅ 最简洁 |
| 与容器耦合 | ✅ 无耦合 | ✅ 无耦合 | ❌ 强耦合 |
| 可选依赖支持 | ❌ 需额外处理 | ✅ 原生支持 | ✅ @Autowired(required=false) |
| Spring 推荐度 | ✅✅✅ 强烈推荐 | ✅ 可选依赖时 | ❌ 不推荐 |
最终建议:
- 首选构造器注入,利用
final关键字保证不可变性和线程安全性 - 对于可选依赖,使用 Setter 注入配合
@Autowired(required=false) - 避免字段注入,除非在无法修改构造器的框架集成代码中
- 使用
@RequiredArgsConstructor(Lombok)简化构造器注入的样板代码 - 关注类的依赖数量——过多的构造器参数(通常 > 5 个)可能意味着类职责过重,应考虑重构