【笔记】Spring 技术解密:Spring MVC 与 Web 环境

概述

过去比较流行的 SSH 技术架构,也就是 Struts + Spring + Hibernate 技术组合,它们是 Web 应用开发中最常用的技术架构之一。这个技术架构是以 Struts 作为 Web 框架来帮助应用构建 UI,Spring 作为应用层平台,Hibernate 作为 O/R 映射的数据持久化层实现。

虽然 SSH 组合中的 Web 层框架是由 Struts 来完成的,但 Spring 自己也带有 MVC 框架为用户开发 Web 应用提供支持,也就是 Spring MVC 模块。为什么叫 MVC 呢?因为它的基础就是 MVC 模式,这个模式的主要特点是:分离了模型、视图、控制器三种角色,将业务处理从 UI 设计中独立出来,封装到模型和控制器中,使得他们相互之间解耦,可以独立扩展而不需彼此依赖。

MVC 模式

在使用 Spring MVC 的时候,需要在 web.xml 中配置 DispatcherServlet,这个 DispatcherServlet 可以看成是一个前端控制器的具体实现,还需要在 Bean 定义中配置 Web 请求和 Controller 的对应关系,以及各种视图的展现方式。在具体使用 Controller 的时候,会看到 ModelAndView 数据的生成,还会把 ModelAndView 数据交给相应的 View(视图)来进行呈现。

本章会对 Spring MVC 的设计进行详细的分析,主要分为两个部分:一个部分侧重于 Spring 的 IoC 容器是怎样在 Web 应用环境中发挥作用的,是如何与 Spring MVC、Struts 等框架进行集成的?另一个部分着重于分析 Spring MVC 框架的实现原理。

Web 环境中的 Spring MVC

在 Web 环境中,Spring MVC 是建立在 IoC 容器基础上的,Spring IoC 是一个独立的模块,并不是直接在 Web 容器中发挥作用的。如果要在 Web 环境中使用 IoC 容器,就需要在 Web 容器启动之后,把 IoC 容器载入进来并初始化。这样,才能建立起 MVC 框架的运行机制。

下面以 Tomcat 作为 Web 容器的例子进行分析:

    <servlet>
        <servlet-name>rootServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>rootServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

在 Tomcat 中,web.xml 是应用部署描述文件,首先定义了一个 Servlet 对象,他是 Spring MVC 的 DispatcherServlet,这个 DispatcherServlet 起着分发请求的作用。在 web.xml 中还定义了 DispatcherServlet 对应的 URL 映射范围。

context-param 参数用于指定 Spring IoC 容器用于读取 bean 定义的 xml 配置文件。

最后,作为 Spring MVC 的启动类,ContextLoaderListener 被定义为一个监听器,这个监听器与 Web 服务器的生命周期相关,负责完成 IoC 容器在 Web 环境中的启动、销毁工作。

总结一下,DispatcherServlet 和 ContextLoaderListener 提供了在 Web 容器中对 Spring 的接口,IoC 容器是通过 ContextLoaderListener 的初始化来建立的,在建立 IoC 容器之后,DispatcherServlet 便负责分发 Web 请求。

上下文在 Web 容器中的启动

ContextLoaderListener 类实现了 ServletContextListener 接口,ServletContextListener 是 servlet api 中的接口,提供了与 Servlet 生命周期结合的回调,比如 contextInitialized 和 contextDestroyed 方法。

由 ContextLoaderListener 启动的是根上下文,还有一个与 MVC 相关的子上下文,保存控制器 DispatcherServlet 需要的 MVC 对象。根上下文的初始化过程如下图所示:

Web 容器中启动 Spring 程序的过程

在 Spring 里默认创建的上下文是 XmlWebApplicationContext,该类继承了 WebApplicationContext,在其基础上增加了对 Web 环境和 xml 配置定义的处理。

Spring MVC 的设计与实现

Spring MVC 的核心便是 DispatcherServlet,DispatcherServlet 实现的是 J2EE 核心模式中的前端控制器模式,作为一个前端控制器,所有的 Web 请求都需要通过它来进行处理,进行转发、匹配、数据处理后,转由页面进行展现。

  1. 在 Spring MVC 中,对于不同 Web 请求的映射需求,提供了不同的 HandlerMapping 实现,可以让应用开发选取不同的映射策略。
  2. Spring MVC 还提供了各种视图实现,比如常用的 JSP 视图、Excel 视图、PDF 视图、Json 视图,为应用开发提供了丰富的视图选择。
  3. 除此之外,还提供了拦截器,允许应用对 Web 请求进行拦截,定制一些前置处理和后置处理。

DispatcherServlet 的启动和初始化

在这里插入图片描述
前面提到,Spring MVC 里有一个子 Context,这个 Context 就是由 DispatcherServlet 来初始化的,DispatcherServlet 类关系图如上所示,其本质上是一个 HttpServlet,具体的初始化流程如下图所示:

DispatcherServlet 初始化流程

MVC 处理 HTTP 分发请求

前面分析了 DispatcherServlet 的初始化过程,下面再讲一下 HTTP 请求的分发处理。HTTP 请求处理的入口一般是 HttpServlet 的 doPost、doGet 等方法,这些方法的实现会调用 DispatcherServlet.doService、doDispatch 方法,主要的请求处理操作都是在 doDispatch 里做的:

	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 {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 获取 HTTP 请求对应的处理器 Handler 信息
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				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)) {
					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;
					}
				}

				// 拦截器前置处理
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 执行请求处理方法
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				// 拦截器后处理
				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);
				}
			}
		}
	}

doDispatch 方法的主要内容是:

  1. 先获取处理器相关的 HandlerExecutionChain
  2. 执行前置拦截器
  3. 执行请求处理方法得到 ModelAndView
  4. 渲染视图
  5. 执行后置拦截器。

HandlerExecutionChain 主要是通过 HandlerMapping 接口的 getHandler 方法来获取,输入 HTTP 请求信息,返回可以处理该请求的 HandlerExecutionChain。HandlerExecutionChain 包含了真正的处理类 handler 和拦截器 interceptorList。handler 对象实际就是 HTTP 请求对应的 Controller。

public interface HandlerMapping {
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

public class HandlerExecutionChain {
	private final Object handler;
	private HandlerInterceptor[] interceptors;
	private List<HandlerInterceptor> interceptorList;
}

在 DispatcherServlet 初始化完成后,在上下文环境中已定义的所有 HandlerMapping 都已经被加载到一个集合 List 中,每一个 HandlerMapping 都对应了一系列从 URL 到 Controller 的映射关系。

Spring MVC 提供了一系列的 HandlerMapping 实现,如下图所示:

HandlerMapping 实现

在 SimpleUrlHandlerMapping 实现中,维护了一个反映映射关系的 handlerMap,当需要匹配 HTTP 请求时,就来 handlerMap 中查询得到对应的 HandlerExecutionChain。这个 handlerMap 是何时配置好的呢?是在容器对 Bean 进行依赖注入时,通过 Bean 的 postProcessor 来完成的。

	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}

		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				} 
			} else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

Spring MVC 视图的呈现

前面分析了 Spring MVC 中 M(Mode) 和 C(Controller) 相关的实现,其中的 M 对应 ModelAndView 的生成,C 对应 DispatcherServlet 和与用户业务逻辑有关的 handler 实现。在 Spring MVC 框架中,DispatcherServlet 起到了非常核心的作用,是整个 MVC 框架的调度枢纽。对于下面关系的视图呈现功能,它的调用入口在 DispatcherServlet 中的 render 方法里。

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale = this.localeResolver.resolveLocale(request);
		response.setLocale(locale);

		View view;
		if (mv.isReference()) {
			// We need to resolve the view name.
			view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isDebugEnabled()) {
			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
						getServletName() + "'", ex);
			}
			throw ex;
		}
	}

从代码中可以看到,视图的呈现过程是,在 ModelAndView 中寻找视图对象的逻辑名,如果已经设置了视图对象的名称,就对这个名称进行解析,从而得到实际需要使用的视图对象。还有一种可能是,ModelAndView 中已经有了视图对象,就可以直接使用。得到视图对象后,直接调用其 render 方法来完成数据的显示过程。

下面我们看一下,如何通过解析视图的逻辑名得到视图对象:

	protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {

		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

ViewResolver 的解析过程可以参考常见的 BeanNameViewResolver:首先取得当前的 IoC 容器,然后判断容器中是否有指定名称的 Bean,如果有,则通过 getBean 方法去取。

	public View resolveViewName(String viewName, Locale locale) throws BeansException {
		ApplicationContext context = getApplicationContext();
		if (!context.containsBean(viewName)) {
			return null;
		}
		if (!context.isTypeMatch(viewName, View.class)) {
			return null;
		}
		return context.getBean(viewName, View.class);
	}

针对不同类型的视图,Spring MVC 提供了不同的实现。Spring 视图的继承体系如下图所示:

Spring MVC View 的继承体系

下面我们以最常见的 JSP 视图为例,看一下其实现原理。在 Spring MVC 中使用 JstlView 来处理 JSP 页面的视图呈现,JstlView 没有实现 render 方法,使用的 render 方法是在基类 AbstractView 中实现的。

	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 所有相关信息收集到 mergedModel
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		// 设置响应 header
		prepareResponse(request, response);
		// 展现数据到视图
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

基类的 render 主要负责完成一些数据的准备工作,然后调用了 renderMergedOutputModel 方法来展示数据到视图。renderMergedOutputModel 的实现位于 InternalResourceView 类:

	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 将 model 里的数据写到 request 的 attributes 里
		exposeModelAsRequestAttributes(model, request);

		// 将一些辅助信息写到 request attributes 里
		exposeHelpers(request);

		// 获取已经定义好的内部资源路径
		String dispatcherPath = prepareForRendering(request, response);

		// 找到对应的请求处理器
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType()); 
			rd.include(request, response);
		} else {
			rd.forward(request, response);
		}
	}

RequestDispatcher 是一个接口类,具体实现是由 Tomcat、Jetty 等完成的,做的事儿就是把 JSP 页面处理好写到 response 里。

发布了232 篇原创文章 · 获赞 347 · 访问量 79万+

猜你喜欢

转载自blog.csdn.net/hustspy1990/article/details/81592423