1、前言
在上一篇《Spring MVC的Servlet容器初始化和请求处理的设计和实现》博客中,我们了解了Servlet及容器Tomcat的相关知识,然后详细的分析了Spring MVC实现Servlet标准体系最基础的两个类HttpServletBean和FrameworkServlet,其中FrameworkServlet类中在处理请求过程中,还涉及到的localeContext、requestAttributes和ServletRequestHandledEvent消息发布的相关内容还没有详细分析,我们这一篇就从这里开始进行分析。
2、LocaleContext
在上一篇中,我们知道在FrameworkServlet类的processRequest()方法中,LocaleContext的处理逻辑如下:
首先,获取LocaleContextHolder原来保存的LocaleContext实例;然后,构建当前请求的localeContext 实例;再把localeContext 实例设置到LocaleContextHolder,然后进行请求的逻辑处理,处理完成后,再恢复LocaleContextHolder原来保存的localeContext。代码如下所示:
```java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//省略……
//获取LocaleContextHolder原来保存的localeContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//获取当前请求的localeContext
LocaleContext localeContext = buildLocaleContext(request);
//省略……
//将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);
//抽象方法,由子类DispatcherServlet实现,请求的实际处理逻辑
doService(request, response);
//恢复原来的LocaleContext和ServletRequestAttributes
resetContextHolders(request, previousLocaleContext, previousAttributes);
//省略……
}
在处理LocaleContext实例的过程中,使用了一个核心的类LocaleContextHolder,该类用来保存当前线程的LocaleContext信息,而且可以在任何需要的地方通过LocaleContextHolder类获取。在该类中,有两个重要定静态变量,如下所示:
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<>("LocaleContext");
private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<>("LocaleContext");
其中,inheritableLocaleContextHolder 可以被子线程继承。实际上,当LocaleContextHolder可以被子线程继承时,LocaleContext实例的实际存储位置就是变量inheritableLocaleContextHolder 中,反之,存储到变量localeContextHolder 中,在其他地方使用LocaleContext实例时,首先获取inheritableLocaleContextHolder 中的数据,为空时,再获取localeContextHolder 中的数据。
在LocaleContext上下文实例中,主要存储了Locale和TimeZone实例,即该实例主要是实现了本地化和时区的上下文。这里不再做深入分析。
// Shared default locale at the framework level
@Nullable
private static Locale defaultLocale;
// Shared default time zone at the framework level
@Nullable
private static TimeZone defaultTimeZone;
3、ServletRequestAttributes
ServletRequestAttributes主要存储了Web请求的相关参数。在FrameworkServlet类的processRequest()方法中过程中,ServletRequestAttributes的处理方式和LocaleContext基本上是一样的。同时也包括了两个ThreadLocal变量,一个用于存储不可继承的变量,一个用于存储可继承的变量,使用逻辑和LocaleContextHolder是一样的,感兴趣的童鞋可以直接看代码。
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
4、ServletRequestHandledEvent
在FrameworkServlet类的processRequest()方法中,通过调用publishRequestHandledEvent()方法,实现ServletRequestHandledEvent事件的触发。实际上,在方法内通过调用webApplicationContext.publishEvent()方法,实现了事件的出发。
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
if (this.publishEvents && this.webApplicationContext != null) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, response.getStatus()));
}
}
在实际应用中,实现了ApplicationListener接口的类,根据ApplicationEvent的类型进匹配,如果符合条件,就会调用onApplicationEvent()方法,从而实现监听器的回调。需要注意的是,该实现类,需要注入到Spring IOC容器中才会生效。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
5、FrameworkServlet中onRefresh()方法的调用时机
在上一篇中,我们提到了在抽象类FrameworkServlet的initWebApplicationContext()方法中,调用了onRefresh()模板方法,该方法由子类进行重写实现,并进行相关组件的初始化,即DispatcherServlet通过重写onRefresh()方法,实现了Spring MVC的初始化工作。
在此之前,我们先补充一下onRefresh()方法的调用时机,在上一篇博客中我们提到了配置或创建webApplicationContext的三种方法,其中只有第二种情况,才会在initWebApplicationContext()方法中调用onRefresh()方法,而第一种和第三种情况,是通过调用configureAndRefreshWebApplicationContext(wac)方法实现了刷新,分析该方法的代码,发现只有wac.refresh();一句代码,这个代码如果实现了调用onRefresh()方法的呢?重新阅读FrameworkServlet的代码,发现有如下的方法:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
在前面分析ServletRequestHandledEvent的时候,我们知道,实现了ApplicationListener接口的类,就可以监听ContextRefreshedEvent 事件,并触发onApplicationEvent()方法的回调。在这里,该方法又调用了在FrameworkServlet类最外层定义的onApplicationEvent(ContextRefreshedEvent event)方法,从而实现了针对容器刷新的监听,当调用wac.refresh();方法时,容器进行了刷新,这个时候就会出发监听事件,最终调用了onRefresh()方法,即前面提到的创建webApplicationContext的另外两种方法,都是通过这种方式实现了onRefresh()方法的调用。
6、DispatcherServlet类-概述
使用过Spring MVC框架的童鞋们都知道,DispatcherServlet类是Spring MVC框架最核心的类,甚至没有之一,该类是请求的核心分发器。在DispatcherServlet类中,除了负责请求的分发,同时在Spring MVC初始化过程中,还负责了Spring MVC框架中九大组件的初始化工作。Spring MVC的九大组件分别是:
- HandlerMapping 根据request找到相应的处理器Handler和Interceptors。
- HandlerAdapter 使用Handler处理器,进行请求处理的组件。
- HandlerExceptionResolver 专门负责对异常情况进行处理的角色。
- ViewResolver 逻辑视图解析器,用来将String类型的视图名和Locale解析为View类型的视图。
- RequestToViewNameTranslator 从request获取viewName,是ViewResolver 解析器的一种补充。
- LocaleResolver 国际化解析器,用于视图解析等。
- ThemeResolver 主题解析器。
- MultipartResolver 附件解析器
- FlashMapManager FlashMap管理器,主要用在处理redirect中传递的参数。
7、Spring MVC组件的初始化
通过前面的分析,我们知道DispatcherServlet通过重写onRefresh()方法,实现了Spring MVC的初始化工作。下面我们从这个方法,开始分析Spring MVC组件的初始化的流程。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
在onRefresh()方法中,通过调用initStrategies()实现了组件的初始化。而initStrategies()方法,通过调用组件对应的initXXX()方法,从而实现组件的初始化过程,这里我们以initHandlerMappings()方法为例,来分析HandlerMapping组件的初始化过程,其他组件的实现方式,基本一样。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//根据detectAllHandlerMappings参数判断是否从容器中,获取全部HandlerMapping类型的实例
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {//赋值给handlerMappings 属性,并排序
this.handlerMappings = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
//获取beanname=handlerMapping的HandlerMapping实例
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
if (this.handlerMappings == null) {
//如果容器中没有指定的HandlerMapping,则获取默认的HandlerMapping。
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
当没有从容器中获取到符合条件的HandlerMapping实例,则通过调用getDefaultStrategies()方法获取默认的HandlerMapping。
注意:因为HandlerMapping组件可能是集合类型,即可以有多个所以调用了getDefaultStrategies()方法,如果只能有一个实例的组件,比如LocaleResolver,则会首先调用getDefaultStrategy()方法,然后在该方法中再调用getDefaultStrategies()方法。
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
//defaultStrategies中保存的是默认的组件配置,在DispatcherServlet.properties中定义。
String value = defaultStrategies.getProperty(key);
if (value != null) {
//字符串转成字符串数组
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
//创建对应组件的实例
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {//如果没有,则返回空,在九大组件中,除了MultipartResolver,其他都有默认的实例
return new LinkedList<>();
}
}
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
return context.getAutowireCapableBeanFactory().createBean(clazz);
}
在上面方法中,变量defaultStrategies的值,由静态代码块,通过加载spring-webmvc.jar包下的DispatcherServlet.properties的文件,进行初始化的,代码如下:
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
8、Spring MVC请求处理
通过前面FrameworkServlet类的学习,我们知道所有的请求,都重新汇聚到了processRequest()方法中,然后再通过调用子类的doService()方法实现请求的处理逻辑,即DispatcherServlet类的请求处理入口是doService()方法。
doService()方法:
在doService()方法中,实际的处理逻辑交给doDispatch()方法进行处理,在该方法中只是做了:首先判断是不是include请求,如果是则对request的Attribute做个快照备份,等doDispatch()处理完之后(如果不是异步调用且未完成)进行还原,在做完快照后又对request设置了一些属性。
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//请求日志记录
logRequest(request);
Map<String, Object> attributesSnapshot = null;
//如果是include请求,则对request的Attribute做个快照备份
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// 对request设置了一些属性
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
//flashMap相关,主要用于Redirect转发时参数的传递
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
//还原request的快照备份
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doDispatch()方法(部分,省略了非逻辑的代码):
在doDispatch()方法中主要做了以下几件事:
- 判断request请求类型,即是否是Multipart类型的请求,如果是并封装request请求
- 根据request找到Handler,如果没有对应的handler,则调用noHandlerFound()进行处理,并返回 (核心)
- 根据Handler找到对应的HandlerAdapter (核心)
- 处理LastModified相关内容
- 调用前置拦截器
- 用HandlerAdapter处理Handler(请求的真正处理逻辑) (核心)
- 使用RequestToViewNameTranslator组件从Request获取mv。
- 调用后置拦截器
- 调用processDispatchResult方法处理上面处理之后的结果,包含找到View并渲染输出给用户 (核心)
- 触发回调函数,并清除相关资源
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//判断是否是Multipart类型,并封装request
processedRequest = checkMultipart(request);
// 根据request找到Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {//没有handler的处理逻辑
noHandlerFound(processedRequest, response);
return;
}
// 根据Handler找到对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {//处理LastModified相关内容
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//调用前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//用HandlerAdapter处理Handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//使用RequestToViewNameTranslator组件从Request获取mv。
applyDefaultViewName(processedRequest, mv);
//调用后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
//调用processDispatchResult方法处理上面处理之后的结果,包含找到View并渲染输出给用户
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//触发afterCompletion回调
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//触发afterCompletion回调
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 当时附件请求时,用于清楚相关资源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch()方法中,通过使用Spring MVC中的组件实现了Web的请求处理过程,后续在分析具体的组件的时候,我们再详细分析该方法中如何使用组件实现相关的功能。