推荐使用 ApplicationContextAware + 封装访问器接口的方式:定义无 Spring 依赖的 AppContextAccessor 接口,由 SpringAppContextAccessor 实现并注入 ApplicationContext,业务组件通过 @Autowired 注入该接口,实现解耦、可测、可替换。

在 Spring 中,自定义组件(如工具类、静态方法、非 Spring 管理的类)想安全、解耦地获取 ApplicationContext,核心是**避免直接依赖容器实现、不暴露静态上下文引用、不破坏依赖注入原则**。所谓“优雅”,不是写个静态工具类就完事,而是让获取方式可测试、可替换、不污染业务逻辑。
用 ApplicationContextAware + 封装访问器(推荐主选)
这是最符合 Spring 设计哲学的方式:由框架主动注入上下文,组件只声明依赖,不主动查找。
- 单独定义一个轻量级访问器接口(如
AppContextAccessor),不带任何 Spring 包依赖,仅声明getBean(Class<T>)或getBean(String, Class<T>)等必要方法 - 实现类(如
SpringAppContextAccessor)实现ApplicationContextAware,内部持有ApplicationContext引用,并委托调用 - 该实现类标记为
@Component,其他自定义组件通过@Autowired注入AppContextAccessor接口,而非直接依赖 Spring 类 - 好处:单元测试时可轻松 Mock 接口;未来若迁出 Spring,只需替换实现类;业务组件完全 unaware 容器细节
通过构造注入或 setter 注入 ApplicationContext
适用于明确由 Spring 管理的组件(如 @Service、@Component),且该组件本身需要动态获取 bean。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 不要用
static字段存储上下文 —— 这会破坏单例语义、引发并发隐患、阻碍容器生命周期管理 - 在构造函数中接收
ApplicationContext参数(需配合@Autowired或构造器注入配置),或提供 setter 方法并标注@Autowired - 这样该组件实例天然持有上下文引用,又保持了依赖显式化和可测性
- 注意:不能用于工具类的静态方法调用场景,但能覆盖绝大多数业务组件需求
Web 环境下用 WebApplicationContextUtils(限定场景)
仅适用于 Servlet 容器环境(如 Tomcat)且无法改造组件生命周期时的兜底方案。
立即学习“Java免费学习笔记(深入)”;
- 通过
ServletContext获取上下文:WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext) - 适合 Filter、Servlet、Listener 等 Web 原生组件中临时使用,不建议在 Service 层或通用工具类中硬编码调用
- 必须确保
servletContext可达(比如从HttpServletRequest获取),否则容易空指针 - 本质是绕过 DI,属于“不得已而为之”,应作为补充而非首选
避免踩坑的硬性约束
真正影响面向接口设计的,往往不是技术选型,而是误用带来的耦合。
- 禁用全局静态
ApplicationContext工具类(如SpringContextHolder)——它把容器强绑定到静态域,导致无法隔离测试、无法多容器共存、违反依赖倒置 - 不手动 new
ClassPathXmlApplicationContext或AnnotationConfigApplicationContext—— 这等于绕过 Spring 生命周期,造成 bean 重复创建、AOP 失效、事务不生效 - 不在工具类里写
public static <T> T getBean(...)—— 静态方法无法被 Spring 管理,也无法注入其他依赖,最终演变成难以维护的“上帝对象” - 接口设计优先考虑“我需要什么能力”,而不是“我怎么从 Spring 拿东西” —— 把
UserService依赖UserRepository,而不是依赖ApplicationContext

















