本文主要讲解Spring MVC 开发时应该了解的一些基本原理。利用Spring MVC咱们通常最多见的有两种使用方式,一种是由view层的好比利用JSP / Velocity/Freemaker来作显示层渲染数据,另外一种没有view层,直接返回相似Json/Xml格式的数据的Restful使用方式。下面先具体讲解下一下Spring MVC原理,而后在讲解下使用RestController注解实现Restful接口的原理。前端
能够说是一个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类须要的几块大内容,包括最主要的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); }
该方法主要是给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); // .....省落.......
这个方法是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);
这个方法主要是处理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结构不须要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 这种能够经过配置灵活扩展的设计思想值得咱们借鉴。