Spring MVC 怎么处理请求
分两步:
- Servlet 处理过程
- DispatcherServlet 核心处理方法doDispatch
1.Servlet 处理过程
1.1 HttpServletBean
只参与了创建工作,没有涉及请求的处理。
1.2 FrameworkServlet
Servlet处理请求都是从Servlet接口的service方法开始,
然后HttpServlet的service方法中根据请求的类型不同,将请求路由到了:
- doGet
- doHead
- doPost
- doPut
- doDelete
- doOptions
- doTrace
七个方法,并且做了 doHead、doOptions和doTrace的默认实现,其中doHead调用doGet,然后返回只有header没有body的response。
- FrameworkServlet 重写了除doHead的所有处理请求的方法。
- 在service方法中增加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理;
- doOptions和doTrace方法可以通过设置
dispatchOpionsRequest
和dispatchTraceRequest
参数决定是自己处理还是交给父类处理(默认都是交给父类处理,doOptions会在父类的处理结果中增加PATCH类型); - doGet、doPost、doPut和doDelete都是自己处理。
- 所有需要自己处理的请求都交给了
processRequest
方法进行统一处理。
查看一下service方法和doGet方法的代码,其它需要自己处理的方法都与doGet方法类似。
// org.springframework.web.servlet.FrameworkServlet
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
// 统一处理请求
processRequest(request, response);
} else {
super.service(request, response);
}
}
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 统一处理请求
processRequest(request, response);
}
在这里所做的事情跟HttpServlet里将来同类型的请求路由到不同方法进行处理的思路正好相反。这里将所有请求又合并到processRequest()
方法中处理。
这埋在不是说Spring MVC中就不对request 的类型进行分类,而全部执行相同的操作了,恰恰相反,Spring MVC对不同类型的请求支持非常好,不过它是通过另一种方式进行处理。
Spring MVC将不同类型的请求用不同的Handler进行处理。
下面来看 FrameworkServlet类中最核心的方法 processRequest()
方法:
// org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 得到与当前请求线程绑定的LocaleContext和ServletRequestAttributes对象
// 然后构造新的Locale和ServletRequestRequestAttributes对象
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 让新构造的LocaleContext和RequestAttributes与当前请求线程绑定(通过ThreadLocal完成)
initContextHolders(request, localeContext, requestAttributes);
try {
// 实际处理请求入口,模板方法,具体由子类DispatcherServlet实现
doService(request, response);
} catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
} catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
// doService方法执行完成之后,重置LocaleContext与RequestAttributes对象。
// 重置也就是解除请求线程与LocaleContext和RequestAttributes对象的绑定。
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
// log代码
logResult(request, response, failureCause, asyncManager);
// 执行成功之后,发布ServletRequestHandledEvent事件
// 可以通过注册监听器来监听该事件的发布。
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
processRequest()
方法中的核心语句是 doService()
,这是一个模板方法,为了在子类 DispatcherServlet 中具体实现。
在 doService()
前后,还做了一些事情(装饰模式):
- 获取了 LocaleContextHolder 和 RequestContextHolder 中原来保存的 LocaleContext 和 RequestAttributes 并设置到 previousLocaleContext 和 previousAttributes 临时属性。
- 调用 buildLocaleContext 和 buildRequestAttributes 方法获取到当前请求的 LocaleContext 和 RequestAttributes。
- 通过initContextHolders()方法将它们设置到 LocaleContextHolder 和 RequestContextHolder中(处理完请求后再恢复到原来的值)
- 接着使用request 拿到异步处理管理器并设置了拦截器,做完这些后执行了doService方法。
- 执行完后,最后(finally中)通过
resetContextHolders()
方法将原来的 previousLocaleContext 和 previousAttributes 恢复到 LocaleContextHolder 和RequestContextHolder 中。 - 调用
publishRequestHandledEvent()
方法发布了一个ServletRequestHandledEvent
类型的消息。
来看一下 LocaleContext和 RequestAttributes.
- LocaleContext 里面存放着 Locale(也就是本地化信息,如 zh-cn 等)
- RequestAttributes 是spring的一个接口,通过它可以 get/set/removeAttribute,根据scope参数判断操作 request 还是 session。
这里具体使用的是 ServletRequestAttributes 类,在 ServletRequestAttributes里面还封装了request、response和session,而且都提供了get方法,可以直接获取。
下面来看一下 ServletRequestAttributes 里 setAttribute 的代码(get/remove都大同小异)
// org.springframework.web.context.request.ServletRequestAttributes
@Override
public void setAttribute(String name, Object value, int scope) {
// 判断是对request还是session进行设置
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
} else {
HttpSession session = obtainSession();
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
- 设置属性时,通过scop 判断是对 request 还是对session 进行设置。
- 注意 isRequestActive() 方法,当调用了 ServletRequestAttributes 的 requestCompleted 方法后 requestActive 就会变为 false,执行之前是true。简单理解就是request执行完了,就不能再操作了。
- LocaleContext 用于获取 Locale
- RequestAttributes 用于管理 request和session 的属性。
接下来看看 LocaleContextHolder 和 RequestContextHolder
LocaleContextHolder
这是一个final类,里面的方法都是static的,可以直接调用,而且没有父类也没有子类。也就是说不能对它进行实例化,只能调用其定义的static方法。
里面定义了两个static属性:
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<>("LocaleContext");
private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<>("LocaleContext");
LocaleContextHolder里面封装了两个属性 localeContextHolder 和 inheritableLocaleContextHolder 它们都是LocaleContext,
LocaleContextHolder 还提供了 get/set 方法,可以获取和设置 LocaleContext ,
另外还提供了 get/setLocale 方法可以直接操作 Locale,是static的方法。这样使用起来非常方便,在程序需要使用 Locale时,可以直接调用。
RequestContextHolder
RequestContextHolder也是同样的道理,里面封装了 RequestAttributes 可以 get/set/removeAttribute, 而且因为实际封装的就是 ServletRequestAttributes ,所以还可以 getRequest、getResponse、getSession,这样就可以在任何想用的地方直接调用。
1.3 DispatcherServlet
DispatcherServlet 是 Spring MVC 最核心的类,整个处理过程的顶层设计都在这里面。
DispatcherServlet 里面执行处理的入口方法应该是 doService , 不过 doService 并没有直接进行处理,而是交给了 doDispatch 进行具体的处理。
在 doDispatch() 处理前 doService() 做了一些事情:
- 首先判断是不是 include 请求,如果是则对 request 的 Attribute 做个快照备份,
- 等 doDispatch 处理完之后(如果不是异步调用且未完成)进行还原,
- 在做完快照后又对 request 设置了一些属性。
doDispatch() 方法非常简洁,从顶层设计了整个请求处理的过程:
doDispatch() 中最核心的代码只有4句:
- HandlerMapping 根据 request 找到 Handler.
- 根据 Handler 找到对应的 HandlerAdapter
- 使用 HandlerAdapter 处理 Handler
- 调用 processDispatchResult 方法处理上面处理之后的结果(包括找到View并渲染输出给用户)
这里需要解释一下三个概念:
- HandlerMapping :是用来查找 Handler 的,每个请求都需要一个 Handler 来处理。
- Handler :就是处理器,对应Controller层,可以是类,也可以是方法。
- HandlerAdapter :Adapter适配器,怎么让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
通俗解释:
- Handler 是用来干活,解决问题的工具。
- HandlerMapping 用于根据需要干的活来找到相应的工具。
- HandlerAdapter 是使用工具(Handler)干活的人。
总体就是,问题(request)来了,HandlerMapping 就会针对这个问题,先找到能解决这个问题的工具(Handler),找到工具(Handler)后,再去找到能使用这个工具的人(HandlerAdapter)。
另外:View 和 ViewResolver 的原理与 Handler 和 HandlerMapping 原理类似。
View 是用来展示数据的,而 ViewResolver 是用来查找 View 的。
通俗的说:就是干完活后需要写报告,写报告又需要模板,View 就是所需要的模板,模板就像报告里的格式,内容就是 Model 里边的数据,ViewResolver 就是用来选择使用哪个模板的。
2.doDispatch()
doDispatch 大体可以分为两部分:
处理请求和渲染页面。
- HttpServletRequest processedRequest: 实际处理时所用的 request,如果不是上传请求则直接使用接收到的 request,否则封装为上传类型的 request.
- HandlerExecutionChain mappedHandler: 处理请求的处理器链(包含Handler处理器和对应的Interceptor拦截器)
- boolean multipartRequestParsed: 是不是上传请求的标志。
- ModelAndView mv: 封装 Model 和 View 的容器,此变量在整个 Spring MVC 处理过程中承担着非常重要的角色。
- Exception dispatchException: 处理请求过程中抛出的异常。需要注意的是它并不包含渲染过程抛出的异常。
小结
Spring MVC 中请求处理过程,先分析三个Servlet ,然后单独分析 DispatcherServlet 中的 doDispatch() 方法。
三个Servlet 的处理过程大致功能如下:
1.HttpServletBean: 没有参与实际请求的处理。
2.FrameworkServlet: 将不同类型的请求合并到了 processRequest 方法统一处理。
processRequest 方法中做了三件事:
- 调用了 doService 模板方法具体处理请求。
- 将当前请求的 LocaleContext 和 ServletRequestAttributes 在处理请求前设置到了LocaleContextHolder 和 RequestContextHolder, 并在请求处理完成后恢复。
- 请求处理完后发布了 ServletRequestHandledEvent 消息。
3.DispatcherServlet: doService 方法给 request 设置了一些属性并将请求交给 doDispatch() 方法具体处理。
doDispatch() 方法中完成了 Spring MVC 处理过程的顶层设计,它使用 DispatcherSevlet 中的九大组件完成了具体的请求处理。