在使用 Spring Boot 开发项目时,我们大多数时候只关心业务 Bean 的注入和使用,很少直接接触 ApplicationContext 这个核心容器。但一旦遇到工具类中需要动态获取 Bean、运行时注册 Bean、或者在框架扩展点中操作容器的场景,对上下文的理解和应用能力就显得尤为重要。本文将系统地讲解 Spring Boot 中如何获取和整合 ApplicationContext,并给出可直接落地的代码实现。

一、ApplicationContext 在 Spring Boot 中的角色与初始化过程
ApplicationContext 是 Spring 的 IoC 容器接口,它继承了 BeanFactory,并在其基础上扩展了国际化、事件发布、资源加载等能力。在 Spring Boot 环境下,容器类型通常是 AnnotationConfigServletWebServerApplicationContext,由 SpringApplication.run() 方法在启动流程中创建。
整个初始化过程大致分为三步:首先通过 createApplicationContext() 根据应用类型推断出具体的容器实现类;接着执行 prepareContext() 完成环境准备、主类注册;最后调用 refreshContext() 触发容器的 refresh,这一步会实例化所有单例 Bean。理解这个时序很关键,因为如果在容器 refresh 之前就尝试获取 Bean,会抛出非法状态异常。
需要特别注意的一点是,Spring Boot 2.x 之后容器继承了 GenericApplicationContext,默认关闭了 Bean 定义重复覆盖。如果在运行时想替换某个 Bean 的定义,需要提前设置 spring.main.allow-bean-definition-overriding=true,否则会直接报错。这是整合上下文时最常踩的坑之一。
二、通过 ApplicationContextAware 实现上下文持有工具类
获取上下文最经典的方案是实现 ApplicationContextAware 接口。Spring 容器在初始化所有实现了该接口的 Bean 时,会自动回调 setApplicationContext() 方法,把容器引用注入进来。我们借助这个机制,可以把上下文保存到静态字段中,供非 Spring 管理的类使用。
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 context) throws BeansException {
SpringContextHolder.applicationContext = context;
}
// 按类型获取 Bean
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
// 按名称获取 Bean
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);
}
}
使用方式非常简单,任何地方调用 SpringContextHolder.getBean(UserService.class) 即可拿到容器中的实例。相比直接使用 @Autowired 注入,这种方式适合在静态方法、工具类、定时任务线程等无法参与依赖注入的场景。
不过静态持有上下文有一个隐患需要注意:如果应用中存在多个 Spring 容器(例如父子容器场景,典型的就是早期 Spring MVC 和 Spring Boot 混合部署),静态变量只会保存最后一次回调的容器引用,可能出现拿到错误容器中 Bean 的问题。此外,如果这个工具类被外部系统长期引用而不释放,会阻止整个容器被垃圾回收,在热部署场景下容易造成堆内存泄漏。解决办法是监听容器的关闭事件,在容器销毁时主动把静态引用置空。
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
@Component
public class ContextCleanupListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
// 容器关闭时清理静态引用,避免内存泄漏
SpringContextHolder.clearHolder();
}
}
三、其他获取上下文的方式对比
除了 Aware 接口回调,Spring Boot 还提供了几种不同的获取途径,它们各有适用场景。最直接的是构造器注入或字段注入,只要当前类本身是 Spring 管理的 Bean,就可以直接注入 ApplicationContext,这是官方最推荐的方式,类型安全且不依赖静态状态。
第二种是启动类入口获取。SpringApplication.run() 方法本身就会返回一个 ConfigurableApplicationContext,可以在 main 方法中直接拿到:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(DemoApplication.class, args);
// 启动后立即获取容器中的 Bean
UserService userService = context.getBean(UserService.class);
userService.doSomething();
}
}
第三种是结合 @PostConstruct 与注入配合,在 Bean 初始化完成后执行一些需要容器参与的逻辑。三种方式的差异可以简单归纳如下:
| 方式 | 适用场景 | 缺点 |
|---|---|---|
| 构造器或字段注入 | Spring 管理的 Bean 内部使用 | 非托管类无法使用 |
| ApplicationContextAware 静态持有 | 工具类、静态方法、非托管对象 | 需防范内存泄漏与多容器问题 |
| 启动类返回值获取 | 启动后立即执行的场景 | 仅限 main 方法作用域 |
四、利用上下文实现运行时动态注册 Bean
拿到上下文引用之后,可以做的不只是 getBean,还可以在运行时动态注册新的 Bean 定义。这在需要根据配置动态装配组件、或者框架设计中按需加载插件的场景下非常实用。借助 DefaultListableBeanFactory 的 registerBeanDefinition() 或 Spring 5 提供的 registerBean() 即可实现。
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
public class DynamicBeanRegister {
public static void register(ConfigurableApplicationContext context,
Class<?> clazz, String beanName) {
DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) context.getBeanFactory();
// 判断是否已存在同名 Bean,避免重复注册
if (beanFactory.containsBeanDefinition(beanName)) {
return;
}
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(clazz);
// 设置构造参数,模拟有参构造
builder.addConstructorArgValue("defaultConfig");
beanFactory.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
public static void unregister(ConfigurableApplicationContext context,
String beanName) {
DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) context.getBeanFactory();
if (beanFactory.containsBeanDefinition(beanName)) {
// 先销毁实例再移除定义,保证清理干净
beanFactory.destroySingleton(beanName);
beanFactory.removeBeanDefinition(beanName);
}
}
}
动态注册时要关注两点:一是注册的时机必须晚于容器 refresh,否则容器还在准备阶段,BeanFactory 尚未就绪;二是销毁 Bean 时不能只调用 removeBeanDefinition,还要调用 destroySingleton,否则已经创建的单例实例仍会驻留在内存中,注册的销毁回调也不会触发。
另一个思路是监听 ContextRefreshedEvent 事件,在容器完全就绪后再执行注册逻辑,这样能保证所有基础 Bean 已经加载完成,动态注册的组件可以安全地依赖它们。这种事件驱动的方式比猜测时序要可靠得多,也是整合上下文扩展功能时的推荐做法。
总结来说,Spring Boot 整合上下文的核心在于理解容器的生命周期,选择合适的获取方式,并对静态持有和动态注册这类高风险操作做好防护。掌握这些技巧后,无论是开发通用组件还是设计框架级扩展,都能更加得心应手。
Spring BootApplicationContextBean管理修改时间:2026-09-06 14:10:36