下面我将从传统方式到现代推荐方式,详细讲解如何在 Spring 中获取 Bean,并分析各自的优缺点和适用场景。

核心概念:IoC 容器 和 Bean
在开始之前,需要理解两个基本概念:
- IoC (Inversion of Control) 控制反转:是一种设计思想,将对象的创建和依赖管理从应用程序代码中转移到外部容器(即 Spring IoC 容器)。
- Bean:由 Spring IoC 容器实例化、组装和管理的对象,你交给 Spring 管理的 Java 对象就是 Bean。
Spring IoC 容器的主要接口是 ApplicationContext,它是 Bean 工厂的核心,提供了更高级的功能。
通过 @Autowired 自动注入 (最推荐)
这是最常用、最符合 Spring 设计思想的方式,你不需要主动去“获取” Bean,而是通过依赖注入,让 Spring 自动将需要的 Bean “注入”到你的类中。
字段注入
直接在类的字段上使用 @Autowired 注解。

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
// @Autowired 也可以省略,前提是启用了 @ComponentScan 并且类只有一个构造器
@Autowired
private UserRepository userRepository;
public String getUserName(Long id) {
return userRepository.findById(id).getName();
}
}
优点:
- 代码简洁,非常直观。
缺点:
- 无法进行单元测试:直接对
UserService进行单元测试时,无法模拟或注入UserRepository,因为字段是private final的,且没有提供 setter。 - 违反了依赖倒置原则:类与 Spring 框架紧密耦合,
@Autowired注解本身是 Spring 特有的。 - 隐藏了依赖关系:类的依赖关系不像构造器注入那样清晰可见。
注意:从 Spring 4.3 开始,如果一个类只有一个构造器,
@Autowired可以省略,Spring 会自动将构造器参数注入。
Setter 注入
通过 public 的 setter 方法来注入 Bean。

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
优点:
- 可进行单元测试:可以轻松地在测试类中调用
setPaymentService()方法来注入 mock 对象。 - 可选依赖:可以将
@Autowired的required属性设置为false(默认为true),表示该依赖是可选的。
缺点:
- 对象可能处于不一致状态:在对象构造完成但 setter 方法被调用之前,对象可能处于一个不完整或无效的状态,另一个线程可能在 setter 调用前就调用了该对象的方法。
构造器注入 (最佳实践)
这是目前公认的最佳注入方式,通过类的构造器来注入所有依赖。
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final InventoryService inventoryService;
private final NotificationService notificationService;
// @Autowired 在只有一个构造器的情况下可以省略
public ProductService(InventoryService inventoryService,
NotificationService notificationService) {
this.inventoryService = inventoryService;
this.notificationService = notificationService;
}
}
优点:
- 依赖不可变:依赖被声明为
final,确保它们在对象创建后不会被修改,线程更安全。 - 依赖关系明确:类的所有依赖都通过构造器参数清晰地暴露出来,一看便知。
- 对象状态一致:在对象被创建时,所有依赖都已准备好,对象总是处于一个完整、有效的状态。
- 易于单元测试:非常容易进行单元测试,只需在
new对象时传入 mock 依赖即可。 - 符合最佳实践:被 Spring 官方和 Java 社区广泛推荐。
通过 ApplicationContext 编程式获取 (不推荐,除非特殊场景)
在某些特殊情况下,你可能无法使用依赖注入,例如在工具类、@Configuration 类的静态方法中,或者在与 Spring 生命周期无关的第三方代码中,这时,你可以通过 ApplicationContext 来手动获取 Bean。
实现 ApplicationContextAware 接口
让你的类实现 ApplicationContextAware 接口,Spring 会在初始化时调用 setApplicationContext() 方法,将 ApplicationContext 实例注入进来。
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextHolder.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
}
使用方法:
// 在其他任何地方都可以这样调用 UserService userService = SpringContextHolder.getBean(UserService.class);
优点:
- 灵活,可以在任何地方获取 Bean。
缺点:
- 强耦合:你的类与 Spring API 紧密耦合,失去了 Spring 的解耦优势。
- 难以测试:单元测试时需要额外处理
ApplicationContext的模拟。 - 代码侵入性强:污染了业务代码,使其不再是纯粹的 POJO。
注入 ApplicationContext
通过 @Autowired 将 ApplicationContext 注入到你的类中,然后使用它来获取 Bean。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
@Autowired
private ApplicationContext applicationContext;
public void someMethod() {
UserService userService = applicationContext.getBean(UserService.class);
// ... 使用 userService
}
}
优缺点:与实现 ApplicationContextAware 类似,同样存在强耦合的问题,只是实现方式略有不同。
通过 @Resource 注入 (JSR-250 标准)
@Resource 是 Java EE 规范(JSR-250)中的注解,Spring 也对其提供了支持,它的注入逻辑与 @Autowired 略有不同。
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class ReportService {
// 默认按名称注入,查找名为 "reportGenerator" 的 Bean
@Resource
private ReportGenerator reportGenerator;
// 可以指定名称
@Resource(name = "emailNotifier")
private Notifier notifier;
// 如果没有 name 属性,则按类型注入,类似于 @Autowired
@Resource
private Logger logger;
}
与 @Autowired 的区别:
@Autowired:默认按类型进行注入,如果找到多个同类型的 Bean,会再按名称进行匹配,可以通过@Qualifier("beanName")来指定具体的 Bean 名称。@Resource:默认按名称进行注入,如果找不到指定名称的 Bean,则会再按类型进行匹配,可以通过@Resource(type = SomeClass.class)来指定类型。
总结与最佳实践
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
@Autowired (构造器) |
依赖不可变、关系清晰、状态一致、易于测试 | 代码比字段注入稍多 | 强烈推荐,适用于绝大多数需要注入依赖的 Spring 管理的 Bean。 |
@Autowired (字段) |
代码简洁 | 无法测试、耦合度高、依赖不明确 | 不推荐,仅适用于快速原型或极简单的组件。 |
@Autowired (Setter) |
可选依赖、可测试 | 对象状态可能不一致 | 适用于可选的、非核心的依赖。 |
ApplicationContext |
灵活,可在非 Spring 管理的类中使用 | 强耦合、代码侵入、难以测试 | 最后的选择,工具类、静态方法、第三方集成等无法使用依赖注入的场景。 |
@Resource |
JSR-250 标准,按名注入 | 逻辑不如 @Autowired 直观 |
当你明确希望按名称注入,或者项目同时使用 Java EE 和 Spring 时。 |
最终建议
- 优先使用构造器注入 (
@Autowiredon constructor):这是最健壮、最清晰、最符合最佳实践的方式,让你的所有依赖都通过构造器声明,final修饰。 - 避免字段注入:除非有非常特殊且合理的理由(某些框架的代理类需要),否则不要在业务代码中使用字段注入。
- 谨慎使用编程式获取:只有在依赖注入完全无法使用的“绝境”中,才考虑通过
ApplicationContext来获取 Bean,这是一种“反模式”,会破坏 Spring 的核心优势。 - 了解
@Resource:知道它的存在和与@Autowired的区别,但在新项目中,通常优先使用@Autowired+@Qualifier的组合来精确控制注入。
