Spring MVC 处理请求

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jeikerxiao/article/details/88579023

Spring MVC 怎么处理请求

分两步:

  1. Servlet 处理过程
  2. DispatcherServlet 核心处理方法doDispatch

1.Servlet 处理过程

1.1 HttpServletBean

只参与了创建工作,没有涉及请求的处理。

1.2 FrameworkServlet

Servlet处理请求都是从Servlet接口的service方法开始,

然后HttpServlet的service方法中根据请求的类型不同,将请求路由到了:

  1. doGet
  2. doHead
  3. doPost
  4. doPut
  5. doDelete
  6. doOptions
  7. doTrace

七个方法,并且做了 doHead、doOptions和doTrace的默认实现,其中doHead调用doGet,然后返回只有header没有body的response。

  1. FrameworkServlet 重写了除doHead的所有处理请求的方法。
  2. 在service方法中增加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理;
  3. doOptions和doTrace方法可以通过设置 dispatchOpionsRequestdispatchTraceRequest参数决定是自己处理还是交给父类处理(默认都是交给父类处理,doOptions会在父类的处理结果中增加PATCH类型);
  4. doGet、doPost、doPut和doDelete都是自己处理。
  5. 所有需要自己处理的请求都交给了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() 前后,还做了一些事情(装饰模式):

  1. 获取了 LocaleContextHolder 和 RequestContextHolder 中原来保存的 LocaleContext 和 RequestAttributes 并设置到 previousLocaleContext 和 previousAttributes 临时属性。
  2. 调用 buildLocaleContext 和 buildRequestAttributes 方法获取到当前请求的 LocaleContext 和 RequestAttributes。
  3. 通过initContextHolders()方法将它们设置到 LocaleContextHolder 和 RequestContextHolder中(处理完请求后再恢复到原来的值)
  4. 接着使用request 拿到异步处理管理器并设置了拦截器,做完这些后执行了doService方法。
  5. 执行完后,最后(finally中)通过resetContextHolders() 方法将原来的 previousLocaleContext 和 previousAttributes 恢复到 LocaleContextHolder 和RequestContextHolder 中。
  6. 调用 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);
	}
}
  1. 设置属性时,通过scop 判断是对 request 还是对session 进行设置。
  2. 注意 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() 做了一些事情:

  1. 首先判断是不是 include 请求,如果是则对 request 的 Attribute 做个快照备份,
  2. 等 doDispatch 处理完之后(如果不是异步调用且未完成)进行还原,
  3. 在做完快照后又对 request 设置了一些属性。

doDispatch() 方法非常简洁,从顶层设计了整个请求处理的过程:

doDispatch() 中最核心的代码只有4句:

  1. HandlerMapping 根据 request 找到 Handler.
  2. 根据 Handler 找到对应的 HandlerAdapter
  3. 使用 HandlerAdapter 处理 Handler
  4. 调用 processDispatchResult 方法处理上面处理之后的结果(包括找到View并渲染输出给用户)

这里需要解释一下三个概念:

  1. HandlerMapping :是用来查找 Handler 的,每个请求都需要一个 Handler 来处理。
  2. Handler :就是处理器,对应Controller层,可以是类,也可以是方法。
  3. HandlerAdapter :Adapter适配器,怎么让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

通俗解释:

  1. Handler 是用来干活,解决问题的工具。
  2. HandlerMapping 用于根据需要干的活来找到相应的工具。
  3. HandlerAdapter 是使用工具(Handler)干活的人。

总体就是,问题(request)来了,HandlerMapping 就会针对这个问题,先找到能解决这个问题的工具(Handler),找到工具(Handler)后,再去找到能使用这个工具的人(HandlerAdapter)。

另外:View 和 ViewResolver 的原理与 Handler 和 HandlerMapping 原理类似。

View 是用来展示数据的,而 ViewResolver 是用来查找 View 的。

通俗的说:就是干完活后需要写报告,写报告又需要模板,View 就是所需要的模板,模板就像报告里的格式,内容就是 Model 里边的数据,ViewResolver 就是用来选择使用哪个模板的。

2.doDispatch()

doDispatch 大体可以分为两部分:

处理请求和渲染页面。

  1. HttpServletRequest processedRequest: 实际处理时所用的 request,如果不是上传请求则直接使用接收到的 request,否则封装为上传类型的 request.
  2. HandlerExecutionChain mappedHandler: 处理请求的处理器链(包含Handler处理器和对应的Interceptor拦截器)
  3. boolean multipartRequestParsed: 是不是上传请求的标志。
  4. ModelAndView mv: 封装 Model 和 View 的容器,此变量在整个 Spring MVC 处理过程中承担着非常重要的角色。
  5. Exception dispatchException: 处理请求过程中抛出的异常。需要注意的是它并不包含渲染过程抛出的异常。

doDispatcher

小结

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 中的九大组件完成了具体的请求处理。

展开阅读全文

没有更多推荐了,返回首页