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字段的构造器
    // 无需手动编写构造器代码
}
相关推荐
一个大专生的淘汰之路2 小时前
Elasticsearch 中的 term的查询
后端
shepherd1112 小时前
从入门到实践:玩转分布式链路追踪利器SkyWalking
java·后端·架构
最贪吃的虎2 小时前
网络是怎么传输的:从底层协议到浏览器访问网站的全过程剖析
java·开发语言·网络·http·缓存
uup2 小时前
CompletableFuture 异常吞噬:异步任务异常未处理导致结果丢失
java
小小8程序员2 小时前
springboot + vue
vue.js·spring boot·后端
有一个好名字2 小时前
设计模式-工厂方法模式
java·设计模式·工厂方法模式
篱笆院的狗2 小时前
Java 中线程之间如何进行通信?
java·开发语言
最贪吃的虎2 小时前
MySQL调优 一:慢SQL日志
运维·数据库·后端·mysql