11.4、Spring源码学习 ——SpringMVC 之 DispatcherServet 的运行阶段

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/bestcxx/article/details/99813923

前言

体能状态先于精神状态,习惯先于决心,聚焦先于喜好

先了解下 DispatcherServet 的特性

DispatcherServet 的 UML 图

在这里插入图片描述

HttpServlet 的特性

DispatcherServet 会拥有 HttpServlet 的基本特性,比如初始化、访问阶段等等。
SpringMVC 之 HttpServlet 和 DispatcherServet

在 FrameworkServlet 中覆盖重写 doGet()等方法

Spring 在抽象类org.springframework.web.servlet.FrameworkServlet中覆盖重写了doGet、doPost、doPut、doDelete、doOptions、doTrace方法。
而doGet、doPost、doPut、doDelete 内部统一调用了 processRequest(request, response); 方法,下面代码以doGet为例

/**
	 * Delegate GET requests to processRequest/doService.
	 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
	 * with a {@code NoBodyResponse} that just captures the content length.
	 * @see #doService
	 * @see #doHead
	 */
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}

FrameworkServlet .processRequest

基本来说,我们常用的 get、post请求都会被击中在 FrameworkServlet .processRequest 这个方法中进行处理

/**
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
		//使用 ThreadLocal 获取本线程当前的 LocaleContext,debug发现 previousLocaleContext=null
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		//获取 本地语言 如 zh_CN
		LocaleContext localeContext = buildLocaleContext(request);
		//使用 ThreadLocal 获取当前线程的 请求参数,用于后期恢复,debug发现 previousAttributes=null
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		//将请求定到当前请求
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        //获取异步处理器
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//注册异步处理到拦截器中
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //初始化本次请求
		initContextHolders(request, localeContext, requestAttributes);

		try {
		    //具体业务处理,实现在 DispatcherServlet 
			doService(request, response);
		}
		catch (ServletException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
		    //恢复当前线程
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}

			if (logger.isDebugEnabled()) {
				if (failureCause != null) {
					this.logger.debug("Could not complete request", failureCause);
				}
				else {
					if (asyncManager.isConcurrentHandlingStarted()) {
						logger.debug("Leaving response open for concurrent processing");
					}
					else {
						this.logger.debug("Successfully completed request");
					}
				}
			}

			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

SpringMVC 并发下线程安全问题

从源码中可以看到,Spring 在每一次请求前会使用 ThreadLocal获取当前线程的 LocaleContext 和 RequestAttributes ,即当前线程的上下文和参数,然后中间判断是否是并发处理——即支持异步处理的方式不至于访问阻塞。
在访问结束后,源码调用了 resetContextHolders 方法,即将当先线程的 LocaleContext 和 RequestAttributes 进行了恢复。
所以 SpringMVC 对于线程安全的处理思路是:每一个线程自己的上下文初始状态在开始和结束后保持一致,允许多个访问转化到多个线程异步进行。这样每次访问的初始状态总是一致的。SpringMVC 借助ThreadLocal 来完成线程初始状态的记录和访问结束后的恢复。
需要注意的是,SpringMVC 的单例并无法保证你的业务并发问题,比如你在 Controller 写了一个非线程安全的全局变量,那么这就是你自己的操作问题了,所以不建议在单例模式在使用全局共享变量。

SpringMVC 单例的无状态性

SpringMVC 为每一个线程提供了线程私有的上下文——运用 ThreadLocal
并且SpringMVC 提倡使用单例,即所有的访问——当访问地址定位到同一个 Controller 时,共享同一个 Controller 对象.这样的好处是可以避免每次访问都创建 Controller 并在之后销毁,Controller的方法也满足“栈封闭”的思想,也是线程安全的,但是对象毕竟是对象,所以如果你在Controller中有全局变量就要注意了,这可能是线程不安全的因素——所以不建议在Controller中添加可变的全局变量,而是应该保持 SpringMVC的无状态性.

猜你喜欢

转载自blog.csdn.net/bestcxx/article/details/99813923