Spring MVC 工做机制

本文主要讲解Spring MVC 开发时应该了解的一些基本原理。利用Spring MVC咱们通常最多见的有两种使用方式,一种是由view层的好比利用JSP / Velocity/Freemaker来作显示层渲染数据,另外一种没有view层,直接返回相似Json/Xml格式的数据的Restful使用方式。下面先具体讲解下一下Spring MVC原理,而后在讲解下使用RestController注解实现Restful接口的原理。前端

普通Spring MVC模式

Spring MVC 流程

DispatcherServlet

能够说是一个servlet支撑了Spring MVC,这里有必要细讲一下DispatcherServlet。 输入图片说明java

从上面的类图中能够看出DispatcherServlet本质上是一个普通的Servlet类,所以咱们能够把须要使用Spring Mvc的请求的类所有映射到这个Servlet中。咱们学习Servlet时都知道,Servlet经过doService来处理请求,这个doService参数是HttpServletRequest request/HttpServletResponse response这两个,开发web项目必定很熟悉了。Servlet容器(Tomcat/Jetty)帮咱们实例化了这个两个参数,request负责封装请求的参数,response负责由服务器往客户端写数据。那么Spring MVC 无非也是利用这个两个参数来实现数据到服务端的读,而后在把数据经过response写到客户端。好比咱们搭建Spring MVC项目时都会在web.xml中配置以下内容,其实和配置一个普通的servlet没有区别,除了多了和Spring Context交互的部分,从上面的类继承图中FrameworkServlet能拿到Spring Context来作IoC相关的工做:web

<servlet>
   <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指明配置文件的文件名,不使用默认配置文件名-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>
           <!-- 这个主要是由其父类 FrameworkServlet 来处理,为servlet管理Spring Context相关的工做都是由它完成的-->
        </init-param>
        <load-on-startup>1</load-on-startup><!--启动顺序-->
</servlet>
<servlet-mapping>
       <servlet-name>DispatcherServlet</servlet-name>
       <url-pattern>/</url-pattern> <!--拦截URL带“/”的请求。-->
</servlet-mapping>

DispatcherServlet主要方法。

initStrategies(ApplicationContext context)

这个方法主要是初始化DispatcherServlet类须要的几块大内容,包括最主要的handlerMapping和viewResolver。spring

//该方法主要是来初始化DispatcherServlet相关的内容的。
	protected void initStrategies(ApplicationContext context) {
        //这主要是填写上传文件时用哪一个MultipartResolver,一般咱们会配个CommonsMultipartResolver
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
    //在容器中拿到全部的Spring Context中的Controller ,放倒handlerMapping list中。
		initHandlerMappings(context);
		initHandlerAdapters(context);
    //异常处理配置
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
    // 配置显示层,好比咱们是用jsp 仍是velocity等等,默认用InternalResourceViewResolver,这个咱们都均可以在配置文件里灵活的更改。
		initViewResolvers(context);
		initFlashMapManager(context);
	}

void doService(HttpServletRequest request, HttpServletResponse response)

该方法主要是给request添加一些属性,好比把applicationContext添加到其属性上,使其在后面处理过程,更容易拿到applicationContext, 也会添加LOCALE_RESOLVER_ATTRIBUTE,THEME_RESOLVER_ATTRIBUTE等等。这个方法最重要的一句代码是 doDispatch(request, response),起到具体分发请求,处理请求的做用,下个方法具体分析。缓存

//.....省落.......
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// .....省落.......
doDispatch(request, response);
// .....省落.......

doDispatch(request, response)

这个方法是Dispatcher最主要的方法:1 负责查找请求和handler(就是咱们写的Controller)的映射,具体到该请求所对应的具体的Controller方法,handler对应的Spring beanFactory,该请求对应的拦截器等; 2 处理文件上传,及加入管理异步请求的逻辑。服务器

doDispatch关键代码,下面是一个基本的请求处理流程app

// Determine handler for the current request. 拿到对应的handler
mappedHandler = getHandler(processedRequest);

// 找到handler 对应的适配器,适配器的做用是起到对handler调用时作一些额外的事情
/*
默认的 SimpleControllerHandlerAdapter 是什么事情都没有作,直接触发handler方法,
这个HandlerAdapter接口设计主要是为了一些定制MVC workflow。This interface is not intended for application developers. It is available to handlers who want to develop their own web workflow.
*/
// 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
			return;
	}
}
// 触发拦截器的 preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
}

// Actually invoke the handler.触发具体的controller逻辑,生成modelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//设置view
applyDefaultViewName(processedRequest, mv);
//触发拦截器的 postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

//处理result,下面单独分析
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

processDispatchResult 方法

这个方法主要是处理handler产生的结果或者异常,若是有异常则调用咱们配置好的异常处理的resolver来处理,若是没有则调用具体的View来渲染handler产生的model。异步

boolean errorView = false;

		if (exception != null) {
        //处理异常
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
           //渲染结果
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
		        。。。。。。。。
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}

Spring MVC Restful模式

输入图片说明 由流程图咱们能够查出Spring MVC Restful结构不须要view来渲染界面。下面咱们看其实现原理。jsp

开发Rest接口用到了RestController注解。从RestController这个注解继承中,看出它继承了Controller和ResponseBody两个注解,也就是说 若是咱们在一个Controller类上加上@RestController 等价于 @Controller 和@ResponseBody这个两个注解。也就是Spring MVC对有注解 ResonseBody 的方法的返回值作了特殊处理,这里咱们还常常用到RequestBody这个注解放到具体的参数bean前,这样会自动把前端提交的内容转换为咱们想要的对象。post

这里先简单理解下,由于Http消息协议都是字符串,也就是说Spring MVC把请求的字符串帮咱们转换为java 对象,处理完逻辑后,在把java 对象转换为Json或者xml字符串写出去。这里有几个类必须提一下:

HttpInputMessage:这个Http消息流向服务端的一个接口抽象

HttpOutputMessage:Http消息写出的抽象接口

HttpMessageConverter :消息转换基本接口类

public interface HttpMessageConverter<T> {


	boolean canRead(Class<?> clazz, MediaType mediaType);


	boolean canWrite(Class<?> clazz, MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();


	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;


	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

知道大概有这么回事,那Spring MVC的DispatcherServlet是怎么把这些转换串起来的呢,下面咱们接着看。

记得咱们上面提过DispatcherServlet的initStrategies方法,里面讲到它帮咱们作了不少初始化工做,包括初始化handler及handlerAdapter,咱们一直没看到handlerAdapter起到什么大做用。其实咱们的消息转换就是靠它帮咱们织入的。在RequestMappingHandlerAdapter中咱们能够看到messageConverters属性,它是一个HttpMessageConverter List,包含了咱们须要的各类消息转换器。

咱们断点调试时能够看到,在doDispatch根据handler取到对应handlerAdapter,里面包含了各类messageConverter。 输入图片说明

输入图片说明

那准备工做完了之后,这些messageConverts在什么时候何地被谁调用呢,继续跟下去 输入图片说明 RequestMappingHandlerAdapter中 输入图片说明

先封装一个invocableMethod出来,设置入参(methodArgument)和出参(methodReturnValue)的resolvers,接着看下面具体的触发过程

输入图片说明 输入图片说明

上面的returenValueHandlers就是刚刚咱们上面set的出参(methodReturnValue)的resolvers,接着下去,找到能处理咱们Response注解的resolver类 RequestResponseBodyMethodProcessor

输入图片说明

RequestResponseBodyMethodProcessor 在其父类方法中遍历converts,找到匹配的messageConvert而后进行转换,而后写出。至此处理过程结束,即时走到DispatcherServlet中的processDispatchResult,因为view为空,因此不会作渲染的逻辑。

至此,SpringMVC 两种常见的用法分析完毕,从Spring MVC的dispatcher类的设计中,咱们能够看到不少处理逻辑是灵活可配置的,好比当咱们用不一样的view显示就直接在xml中配对应的ViewResolver,例如常见的用JSP时,咱们常常会像下边这样配置

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</bean>

还有若是上传文件时,咱们会配置multipartResolver,像下边这样

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

还有上面咱们讲到作消息转换用到的handlerAdapter和messageConvert,均可以灵活的配置的。Spring MVC 这种能够经过配置灵活扩展的设计思想值得咱们借鉴。

https://my.oschina.net/robinyao/blog/795723

相关文章
相关标签/搜索