本文主要讲解Spring MVC 开发时应该了解的一些基本原理。利用Spring MVC我们一般最常见的有两种使用方式,一种是由view层的比如利用JSP / Velocity/Freemaker来做显示层渲染数据,另一种没有view层,直接返回类似Json/Xml格式的数据的Restful使用方式。下面先具体讲解下一下Spring MVC原理,然后在讲解下使用RestController注解实现Restful接口的原理。
普通Spring MVC模式
DispatcherServlet
可以说是一个servlet支撑了Spring MVC,这里有必要细讲一下DispatcherServlet。
从上面的类图中可以看出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相关的工作:
<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。
//该方法主要是来初始化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关键代码,下面是一个基本的请求处理流程
// 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来渲染界面。下面我们看其实现原理。
开发Rest接口用到了RestController注解。从RestController这个注解继承中,看出它继承了Controller和ResponseBody两个注解,也就是说 如果我们在一个Controller类上加上@RestController 等价于 @Controller 和@ResponseBody这个两个注解。也就是Spring MVC对有注解 ResonseBody 的方法的返回值做了特殊处理,这里我们还经常用到RequestBody这个注解放到具体的参数bean前,这样会自动把前端提交的内容转换为我们想要的对象。
这里先简单理解下,因为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