Marco's Java【SpringMVC进阶(二) 之 SpringMVC源码解析】

前言

上回在 Marco’s Java【SpringMVC番外篇 之 映射器及适配器运行原理源码解析】 我已经带大家看过Spring中的两个模块映射器适配器的源码并解析了这两个模块是如何配合运行的,以及DisptcherServelt的初始化规则,那么本节我们结合SpringMVC再系统的讲解Spring的初始化运行机制以及页面请求的分发处理。

源码跟进

那么此次呢,我们还是从再熟悉不过的"门卫大叔"DisptcherServelt为切入点来深入分析,通过配置取消懒加载,当服务器启动时创建DisptcherServelt对象
在这里插入图片描述
我们知道,在初始化DisptcherServelt对象之后呢,肯定伴随着其他的配置的初始化,那么一般这个方法都是叫init(),或者以init开头的方法,那么我们来找找看这个方法是否存在。
我们浏览了DisptcherServelt的方法,并没有发现类似方法,那么我们继续查找它的父类FrameworkServlet,发现依然没有类似的方法?
不要放弃,我们再找找看FrameworkServlet的父类HttpServletBean。发现还真的有一个方法叫init(),赶紧点开看看里面的内容!

/**
* Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {
	//打印日志
	if (logger.isDebugEnabled()) {
		logger.debug("Initializing servlet '" + getServletName() + "'");
	}
	// 获取web.xml中的初始化参数
	// Set bean properties from init parameters.
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			// 使用BeanWrapper构造DispatcherServlet
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			// 设置DispatcherServlet的属性
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	// Let subclasses do whatever initialization they like.
	//这是一个空的实现方法,默认由它的子类去实现
	initServletBean();

	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

看到这里我们发现了一个空的方法initServletBean(),那么肯定是有子类去实现它的,我们先在当前初始化对象DisptcherServelt中找找看initServletBean()方法,发现好像没有,我们继续在父类FrameworkServlet中查找

/**
 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 * have been set. Creates this servlet's WebApplicationContext.
 */
@Override
protected final void initServletBean() throws ServletException {
	//打印日志
	getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
	}
	long startTime = System.currentTimeMillis();
	
	try {
		// 初始化WebApplicationContext这个IOC容器的属性
		this.webApplicationContext = initWebApplicationContext();
		// 初始化FrameworkServlet对象,这是一个空方法
		initFrameworkServlet();
	} //下面都是异常的捕捉,我就不分析啦
	catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}
	catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (this.logger.isInfoEnabled()) {
		long elapsedTime = System.currentTimeMillis() - startTime;
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
				elapsedTime + " ms");
	}
}

通过上面的代码,我们发现下面这段有待考究的代码
this.webApplicationContext = initWebApplicationContext();
webApplicationContext实质上就是一个承载web项目中的MVC模型中所有对象的容器,这点和我们的applicationContext很相似,那么我们把这个方法搬过来

/**
 * Initialize and publish the WebApplicationContext for this servlet.
 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
 * of the context. Can be overridden in subclasses.
 * @return the WebApplicationContext instance
 * @see #FrameworkServlet(WebApplicationContext)
 * @see #setContextClass
 * @see #setContextConfigLocation
 */
protected WebApplicationContext initWebApplicationContext() {
	//获取WebApplicationContext实例对象rootContext
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		//判断这个WebApplicationContext对象是哪一个具体的子类的对象
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		// No context instance was injected at construction time -> see if one
		// has been registered in the servlet context. If one exists, it is assumed
		// that the parent context (if any) has already been set and that the
		// user has performed any initialization such as setting the context id
		//看看当前的context上下文对象是否已经在WebApplicationContext容器中注册
		wac = findWebApplicationContext();
	}
	if (wac == null) {
	 	// 创建web上下文对象
		// No context instance is defined for this servlet -> create a local one
		//如果说这个servlet中还没有上下文对象,则创建一个context上下文对象
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
		// Either the context is not a ConfigurableApplicationContext with refresh
		// support or the context injected at construction time had already been
		// refreshed -> trigger initial onRefresh manually here.
		// 执行DispatcherServlet的实现 初始化springmvc九大组件,例如我们的适配器,映射器,视图解析器等等
		onRefresh(wac);
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		//将这个上下文对象发布出去,放在最大的ServletContext作用域中
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
					"' as ServletContext attribute with name [" + attrName + "]");
		}
	}
	return wac;
}

通过上面的三个if语句,我们发现,我们有三种不同的应对策略完成WebApplicationContext的创建,但是最终还是会执行onRefresh(wac)方法。
onRefresh(wac)的作用就是执行DispatcherServlet的实现 初始化springmvc九大组件,例如我们的适配器,映射器,视图解析器等等,我们发现DispatcherServlet重写了这个方法,而onRefresh()实际上是调用initStrategies(context)方法
在这里插入图片描述

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);//文件上传解析器
	initLocaleResolver(context);//本地化解析器
	initThemeResolver(context);//主题解析器
	initHandlerMappings(context);//处理器映射
	initHandlerAdapters(context);//处理器适配器
	initHandlerExceptionResolvers(context);//处理器异常解析器
	initRequestToViewNameTranslator(context);//请求转视图名翻译器
	initViewResolvers(context);//视图解析器
	initFlashMapManager(context);//FlashMap管理
}

到这里为止是不是和我们之前的内容对接上了?接下来的内容我就不重复啦,大家可以看之前的文章,它的作用就是初始化springmvc九大组件

接下来我们再针对于客户页面请求的这一块再来分析一下,看DispatcherServlet是如何分发并执行请求的,这一部分在我们的SpringMVC中显得尤为的重要。
在Sevelt中,如果想要处理页面的请求和响应就必须继承HttpServlet,并重写父类的sevice方法(或者重写doGet和doPost方法),那么根据这个线索,我们来找找看DispatcherServlet,和他的父类中是否有这个方法。

我们发现FrameworkServlet重写了service方法

/**
 * Override the parent class implementation in order to intercept PATCH requests.
 */
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	//httpMethod中封装了请求的类型,例如GET、POST等
	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
		processRequest(request, response);
	}
	else {
		//调用HttpServlet中的service方法
		super.service(request, response);
	}
}

根据上面的代码我们发现,当请求方式为PATCH或者获取到的请求的方式不存在的时候,会执行processRequest,否则直接调用HttpServlet的service方法

protected void service(HttpServletRequest req, HttpServletResponse resp)
     throws ServletException, IOException {

     String method = req.getMethod();

     if (method.equals(METHOD_GET)) {
         long lastModified = getLastModified(req);
         if (lastModified == -1) {
             // servlet doesn't support if-modified-since, no reason
             // to go through further expensive logic
             doGet(req, resp);
         } else {
             long ifModifiedSince;
             try {
                 ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
             } catch (IllegalArgumentException iae) {
                 // Invalid date header - proceed as if none was set
                 ifModifiedSince = -1;
             }
             if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                 // If the servlet mod time is later, call doGet()
                 // Round down to the nearest second for a proper compare
                 // A ifModifiedSince of -1 will always be less
                 maybeSetLastModified(resp, lastModified);
                 doGet(req, resp);
             } else {
                 resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
             }
         }

     } else if (method.equals(METHOD_HEAD)) {
         long lastModified = getLastModified(req);
         maybeSetLastModified(resp, lastModified);
         doHead(req, resp);

     } else if (method.equals(METHOD_POST)) {
         doPost(req, resp);

     } else if (method.equals(METHOD_PUT)) {
         doPut(req, resp);

     } else if (method.equals(METHOD_DELETE)) {
         doDelete(req, resp);

     } else if (method.equals(METHOD_OPTIONS)) {
         doOptions(req,resp);

     } else if (method.equals(METHOD_TRACE)) {
         doTrace(req,resp);

     } else {
         //
         // Note that this means NO servlet supports whatever
         // method was requested, anywhere on this server.
         //

         String errMsg = lStrings.getString("http.method_not_implemented");
         Object[] errArgs = new Object[1];
         errArgs[0] = method;
         errMsg = MessageFormat.format(errMsg, errArgs);

         resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
     }
 }

而当HttpServlet的service方法被调用,实际上就是调用它的doPost()和doGet()方法,而FrameworkServlet恰好又重写了doPost()和doGet()方法,因此当一个post/get的请求进来之后,会优先调用FrameworkServlet中的doGet/doPost方法,其实,我们追踪FrameworkServlet的这两个方法最终会发现,其实本质上到最后依然是调用
processRequest(request, response)这个方法。
那么接下来我们先来分析一下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;
	
	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());
	//把HttpServletRequest的封装对象ServletRequestAttributes与当前线程绑定。
	initContextHolders(request, localeContext, requestAttributes);

	try {
		//执行请求
		doService(request, response);
	}
....... //下面的异常处理代码我就省略了,突出重点

上面的这个方法最终执行的是DispatcherServelt中的doService(request, response)方法,而我们再往下追溯,会发现,其实他调用的是doDispatch()核心方法,该方法的作用也恰如其名,实现请求的分发处理,这一部分我们之前在 Marco’s Java【SpringMVC番外篇 之 映射器及适配器运行原理源码解析】 中也详细讲到过了,我们再来回顾一下,细节部分可以再看看这篇文章的内容
在这里插入图片描述

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;//模型视图,存放跳转的访问路径和要进行转发的值
		Exception dispatchException = null;

		try {
			//如果是MultipartContent类型,就转换为MultiHttpServletRequest的request
			//主要是为了处理文件上传类型的请求
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);
			//通过请求对象,根据花名册获取到对应的处理器
			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);
			//若找不到对应的处理器,则执行noHandlerFound方法
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				noHandlerFound(processedRequest, response);
				return;
			}
			//根据获取到的处理器,获取到相应的适配器
			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
			// 如果当前handler支持last-modified头处理
			// Process last-modified header, if supported by the handler.
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (logger.isDebugEnabled()) {
					logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			//拦截器拦截处理prehandle中的方法处理,如果prehandle返回为false,则拦截处理,直接return
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}
			//处理器处理当前的请求,返回ModelAndView模型视图对象
			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}
			//处理模型视图对象
			applyDefaultViewName(processedRequest, mv);
			//拦截器拦截处理posthandle中的方法处理,在方法结束后被调用,一般用于ModelAndView的处理
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		catch (Throwable err) {
			// As of 4.3, we're processing Errors thrown from handler methods as well,
			// making them available for @ExceptionHandler methods and other scenarios.
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		//处理最终的结果,并进行视图的渲染
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Throwable err) {
		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 {
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				//清除请求为文件上传类型的流的资源占用
				cleanupMultipart(processedRequest);
			}
		}
	}
}

其实到目前为止,我们解析的内容就和上回源码分析的内容结合在一起了。
那么总结一下我们的执行流程,首先是通过web.xml执行DispatcherServlet的初始化,并获取springmvc.xml配置文件的路径,初始化控制器映射器(花名册),当客户端发出请求时,DispatcherServlet通过花名册获取控制器对象,并且找到该控制器的适配器组装,在执行控制器的方法之前,预先经过拦截器的方法preHandle,然后等待方法结束并返回ModelAndView之前,再次执行调用postHandle的方法,处理我们的模型视图对象,处理完成并在视图对象返回给DispatcherServlet的途中,再执行拦截器的afterCompletion方法。最后由DispatcherServlet交给视图渲染器渲染处理。

此时大家回过头来再看一下我们之前画的这两张图,相信大家应该对SpringMVC的执行流程有一个清晰的认知啦,那么为了再次加深对SpringMVC源码的理解,下一节我们来手写Spring这一块的执行代码,期待一下吧!
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值