Spring Framework源码解析——ServletContextAware
版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl
一、概述
ServletContextAware
是 Spring 框架提供的一个回调接口(Callback Interface),用于在 Bean 初始化过程中自动注入 javax.servlet.ServletContext
对象。该接口属于 Spring 的 Aware 系列接口之一,其设计目的是让容器管理的 Bean 能够感知并访问 Web 应用的 Servlet 上下文环境。
在 Web 应用中,ServletContext
是整个 Web 应用的全局上下文对象,提供了访问 Web 应用配置、资源、属性、日志等能力。通过实现 ServletContextAware
,开发者可以在不依赖硬编码或静态工具类的情况下,安全、规范地获取 ServletContext
实例。
二、接口定义与定位
package org.springframework.web.context;import javax.servlet.ServletContext;
import org.springframework.beans.factory.Aware;public interface ServletContextAware extends Aware {void setServletContext(ServletContext servletContext);
}
- 继承自
org.springframework.beans.factory.Aware
:标记该接口为 Spring 的“感知”接口; - 唯一方法
setServletContext(...)
:由 Spring 容器在 Bean 初始化阶段调用,传入当前 Web 应用的ServletContext
实例。
关键特性:
- 仅在 Web 环境中生效(如
WebApplicationContext
);- 非线程安全方法:仅在 Bean 初始化时调用一次;
- 由容器自动调用,开发者无需手动触发。
三、工作机制:从 Aware 到自动注入
Spring 通过 ApplicationContextAwareProcessor
这一 BeanPostProcessor
实现对 Aware
接口的统一处理。ServletContextAware
的注入正是该机制的一部分。
3.1 核心调用链
- Bean 实例化完成(
InstantiationAwareBeanPostProcessor
阶段结束); - 属性填充完成后,进入初始化前阶段(
postProcessBeforeInitialization
); ApplicationContextAwareProcessor
检测 Bean 是否实现ServletContextAware
;- 若是,则调用其
setServletContext(...)
方法。
3.2 ApplicationContextAwareProcessor
源码节选
// org.springframework.context.support.ApplicationContextAwareProcessor@Override
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||bean instanceof ResourceLoaderAware ||bean instanceof ApplicationEventPublisherAware ||bean instanceof MessageSourceAware ||bean instanceof ApplicationContextAware ||bean instanceof ServletContextAware || // ← 关键判断bean instanceof ServletConfigAware)) {return bean;}// ...if (bean instanceof ServletContextAware) {ServletContext sc = this.applicationContext instanceof WebApplicationContext ?((WebApplicationContext) this.applicationContext).getServletContext() : null;if (sc != null) {((ServletContextAware) bean).setServletContext(sc); // ← 注入}}// ...return bean;
}
关键逻辑:
- 仅当
applicationContext
是WebApplicationContext
子类时,才尝试获取ServletContext
;- 若非 Web 环境(如纯 Java SE 应用),
sc
为null
,不会调用setServletContext
;- 保证了接口的环境安全性。
四、WebApplicationContext
与 ServletContext
的绑定关系
ServletContext
的来源依赖于 WebApplicationContext
的实现。以 XmlWebApplicationContext
为例:
// org.springframework.web.context.support.AbstractRefreshableWebApplicationContextprivate ServletContext servletContext;@Override
public void setServletContext(@Nullable ServletContext servletContext) {this.servletContext = servletContext;
}@Override
@Nullable
public ServletContext getServletContext() {return this.servletContext;
}
在 ContextLoader
初始化根上下文时,会显式设置 ServletContext
:
// ContextLoader.configureAndRefreshWebApplicationContext(...)
wac.setServletContext(sc); // sc = ServletContext
因此,ApplicationContextAwareProcessor
能通过 ((WebApplicationContext) applicationContext).getServletContext()
获取有效实例。
五、使用场景与典型示例
5.1 场景一:读取 Web 应用初始化参数
@Component
public class AppConfig implements ServletContextAware {private String uploadPath;@Overridepublic void setServletContext(ServletContext servletContext) {this.uploadPath = servletContext.getInitParameter("upload.path");}public String getUploadPath() {return uploadPath;}
}
对应
web.xml
:<context-param><param-name>upload.path</param-name><param-value>/var/uploads</param-value> </context-param>
5.2 场景二:获取 Web 资源(如配置文件)
@Override
public void setServletContext(ServletContext servletContext) {try (InputStream is = servletContext.getResourceAsStream("/WEB-INF/custom.properties")) {Properties props = new Properties();props.load(is);// 处理配置} catch (IOException e) {// 异常处理}
}
5.3 场景三:注册自定义属性到 ServletContext
@Override
public void setServletContext(ServletContext servletContext) {servletContext.setAttribute("myAppStartTime", System.currentTimeMillis());
}
注意:此类操作应谨慎,避免污染全局上下文。
六、与其他 Aware 接口的关系
接口 | 注入对象 | 适用环境 |
---|---|---|
ServletContextAware | javax.servlet.ServletContext | Web |
ServletConfigAware | javax.servlet.ServletConfig | Web(Servlet 级别) |
ApplicationContextAware | ApplicationContext | 通用 |
ResourceLoaderAware | ResourceLoader | 通用 |
EnvironmentAware | Environment | 通用 |
优先级:所有 Aware 回调均在
InitializingBean.afterPropertiesSet()
和@PostConstruct
之前执行。
七、生命周期与调用时机
ServletContextAware.setServletContext()
的调用时机如下:
Bean 实例化↓
属性填充(@Autowired、@Value 等)↓
Aware 接口回调(包括 ServletContextAware)↓
BeanPostProcessor.postProcessBeforeInitialization()↓
@PostConstruct / InitializingBean.afterPropertiesSet()↓
BeanPostProcessor.postProcessAfterInitialization()↓
Bean 可用
重要结论:
- 在
setServletContext()
被调用时,Bean 的依赖尚未完全初始化;- 不应在该方法中调用其他 Bean 的业务方法;
- 仅用于保存引用或读取上下文信息。
八、设计思想与架构意义
8.1 控制反转(IoC)的延伸
- 传统方式:通过
WebUtils.getServletContext()
等静态工具类获取; - Spring 方式:由容器主动注入,实现依赖解耦;
- 符合“不要打电话给我们,我们会打给你”(Hollywood Principle)。
8.2 环境感知能力
- 使 Bean 具备“环境上下文感知”能力;
- 支持同一套代码在不同部署环境(如 WAR vs 内嵌容器)中运行。
8.3 类型安全与编译期检查
- 相比于通过
ApplicationContext
手动查找ServletContext
,接口方式提供编译期类型安全; - IDE 可自动提示方法实现。
九、注意事项与最佳实践
9.1 注意事项
- 仅在 Web 环境中有效:在非 Web 的
ApplicationContext
中不会被调用; - 不要存储非线程安全对象:
ServletContext
本身是线程安全的,但从中获取的资源(如InputStream
)需及时关闭; - 避免在
setServletContext
中执行耗时操作:阻塞 Bean 初始化会影响应用启动性能; - 不要依赖其他 Bean:此时其他 Bean 可能尚未初始化。
9.2 替代方案
在 Spring Boot 或基于注解的配置中,更推荐使用:
@Component
public class MyService {@Autowiredprivate ServletContext servletContext;
}
或通过 @Value
读取 context-param
:
@Value("${upload.path:default/path}")
private String uploadPath;
优势:更简洁、符合声明式编程风格;
前提:需确保ServletContext
已注册为 Bean(Spring Boot 自动完成)。
十、总结
ServletContextAware
是 Spring Web 集成体系中的一个精巧设计,它通过标准的回调机制,将 Web 容器的核心上下文对象安全、规范地注入到 Spring 管理的 Bean 中。其背后依托于 ApplicationContextAwareProcessor
和 WebApplicationContext
的协同工作,体现了 Spring 框架对 “环境感知” 与 “控制反转” 原则的深刻实践。
尽管在现代 Spring Boot 应用中,直接实现 ServletContextAware
的场景有所减少,但其设计思想——通过接口契约实现容器与组件的解耦通信——仍然是 Spring 扩展机制的核心范式之一。理解该接口的实现原理,有助于深入掌握 Spring 的生命周期管理、Aware 机制以及 Web 集成架构。