Spring三种注入方式对比

本文将简单介绍Spring三种注入方式:字段注入、setter注入、构造器注入,对比和使用场景。

一、字段注入

字段注入是通过在字段上直接使用@Autowired注解来实现的,Spring容器会利用反射机制直接设置字段的值。仅适用于简单场景。使用示例如下:

java 复制代码
@Component
public class UserService {
    @Autowired  // 字段注入
    private UserRepository userRepository;
    
    @Resource(name = "emailService")  // 字段注入
    private EmailService emailService;
}

@Autowired和@Resource 对比

@Autowired默认按类型查找

如果找到多个,按以下顺序尝试:1、查找有 @Primary 注解的bean;2、查找有 @Priority 注解的bean(值越小优先级越高)

如果都没有找到,则按属性名匹配(Spring 4.3+);如果仍然有歧义,抛出异常

@Resource 默认按name属性名称查找(即使类型不完全匹配,只要名称对就注入) → 找不到则抛出异常

如果未指定 name 属性:则按字段/方法名查找

特性 @Autowired @Resource
所属规范 Spring 框架原生注解 JSR-250 (Java EE 标准)
默认装配方式 按类型 (byType) 按名称 (byName)
是否支持构造器注入 ✅ 支持 ❌ 不支持
是否支持可选依赖 ✅ (required=false) ✅ (找不到则跳过)
是否支持集合注入 ✅ 支持 ❌ 不支持
与 @Qualifier 配合 ✅ 需要配合使用 ✅ 自身可指定 name
Spring 推荐度 ✅ 推荐 ⚠️ 可用但非首选
优缺点:

**优点:**1、简洁性:代码简洁,减少样板代码;2、易于理解:依赖声明和使用在同一位置

**缺点:**1、不可变性:无法声明为final,线程安全性需额外注意;2、依赖隐藏:从类的外部无法直观看出依赖关系;3、测试困难:需要反射或Spring容器才能注入依赖;4、违反单一职责:容易导致类拥有过多依赖;5、循环依赖风险:容易掩盖设计问题

二、Setter注入

通过setter方法在对象创建后注入依赖。

可以在对象创建后重新注入依赖;可选依赖:适合非必需依赖的注入;可读性强:方法名明确表示设置的是什么依赖

使用示例如下:

java 复制代码
// 1. XML配置方式
<bean id="orderService" class="com.example.OrderService">
    <property name="paymentService" ref="paymentService"/>
    <property name="timeout" value="5000"/>
</bean>

// 2. 注解方式
@Service
public class OrderService {
    private PaymentService paymentService;
    private int timeout;
    
    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    @Value("${order.timeout:3000}")
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
    
    // 也支持在字段上直接注解
    @Autowired
    private NotificationService notificationService;
}
优缺点:

**优点:**1、灵活性:可以在对象创建后重新注入依赖;2、可选依赖:适合非必需依赖的注入;3、可读性强:方法名明确表示设置的是什么依赖

**适用场景:**1、可选依赖;2、需要重新配置依赖的场景;3、遗留代码或需要遵循JavaBean规范的场景

三、构造器注入(首推依赖注入方式)

通过类的构造方法来注入依赖,在对象创建时即完成所有依赖的注入。

优点不可变性 :依赖可声明为 final,确保线程安全;完全初始化 :对象创建时即获得所有必需依赖,保证可用性;明确的依赖关系:从构造器签名即可看出类的依赖需求;便于测试 :无需容器即可进行单元测试;循环依赖检测:Spring能早期发现构造器循环依赖问题

使用示例如下:

java 复制代码
// 1. 传统XML配置方式
<bean id="userService" class="com.example.UserService">
    <constructor-arg ref="userRepository"/>
    <constructor-arg value="defaultUser"/>
</bean>

// 2. Java配置类方式
@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService(userRepository(), "defaultUser");
    }
}

// 3. 注解方式(最常用)
@Service
public class UserService {
    // final字段确保依赖在构造时就可用
    private final UserRepository userRepository;
    private final String defaultUser;
    
    
    public UserService(UserRepository userRepository, 
                      @Value("${default.user}") String defaultUser) {
        this.userRepository = userRepository;
        this.defaultUser = defaultUser;
    }
}

三种注入方式对比

特性 字段注入 Setter注入 构造器注入
不可变性 ❌ 不支持 ❌ 不支持 ✅ 支持
循环依赖检测 ⚠️ 运行时检测 ⚠️ 运行时检测 ✅ 早期检测
测试友好性 ❌ 困难 ✅ 良好 ✅ 优秀
代码简洁性 ✅ 优秀 ✅ 良好 ⚠️ 一般
明确依赖关系 ❌ 隐蔽 ✅ 明确 ✅ 明确
Spring官方推荐 ❌ 不推荐 ⚠️ 特定场景 ✅ 推荐
为什么推荐构造器注入,而不是@Autowired

1、在对象的生命周期里,**构造方法永远优先于依赖注入。**这意味着在构造方法执行时,依赖的bean还没有被注入,因此如果在构造方法中使用了依赖的bean,会导致NullPointerException)。如果使用构造器注入,那么依赖的bean会在调用构造方法之前由Spring容器准备好,并作为参数传入,因此在构造方法中可以使用这些依赖。

2、@Autowired 采用三级缓存会掩盖 循环依赖的问题,而构造方法会直接暴露出来

3、测试过程中,@Autowired注入方式通常需要Mock注入的方式,构造器注入易于测试

注入实现方式示例

混合使用策略

java 复制代码
@Component
public class MixedInjectionService {
    // 必需依赖:构造器注入
    private final RequiredService requiredService;
    private final ConfigProperties config;
    
    // 可选依赖:Setter注入
    private OptionalService optionalService;
    
    public MixedInjectionService(RequiredService requiredService, 
                                ConfigProperties config) {
        this.requiredService = requiredService;
        this.config = config;
    }
    
    @Autowired(required = false)
    public void setOptionalService(OptionalService optionalService) {
        this.optionalService = optionalService;
    }
}

使用Lombok简化注入

java 复制代码
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final EmailService emailService;
    
    // Lombok自动生成包含所有final字段的构造器
    // 无需手动编写构造器代码
}
相关推荐
码云数智-大飞几秒前
零拷贝 IPC:用内存映射文件打造 .NET 高性能进程间通信队列
java·开发语言·网络
懈尘3 分钟前
深入理解Java的HashMap扩容机制
java·开发语言·数据结构
indexsunny4 分钟前
互联网大厂Java面试实战:从Spring Boot到Kafka的技术与业务场景解析
java·spring boot·redis·面试·kafka·技术栈·microservices
roman_日积跬步-终至千里6 分钟前
【Java并发】Tomcat 与 Spring:后端项目中的线程与资源管理
java·spring·tomcat
独自破碎E7 分钟前
IDEA 提示“未配置SpringBoot配置注解处理器“的解决方案
java·spring boot·intellij-idea
yqd6667 分钟前
RabbitMQ用法和面试题
java·开发语言·面试
2601_9498095910 分钟前
flutter_for_openharmony家庭相册app实战+照片详情实现
android·java·flutter
程序员泠零澪回家种桔子10 分钟前
OpenManus开源自主规划智能体解析
人工智能·后端·算法
4311媒体网12 分钟前
Libvio.link 页面布局与数据分布
java·php
奋斗的小方16 分钟前
01 一文读懂UML类图:核心概念与关系详解
java·uml