概述
本文中主要说明 Spring MVC 消息转换器的选择,也是记录我在工作中遇到的问题。
我的 spring mvc 版本是 4.3.10
遇到的问题:处理问题是如果 spring mvc 无法接受 json 请求,如果我传 json 格式的参数过去就返回 415 消息格式异常
Spring MVC 消息解析源码
spring-mvc.xml 配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
<!-- 扫描web包,应用Spring的注解 -->
<context:component-scan base-package="cn.edu.cqvie.mvc.controller"/>
<!-- jsp视图 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 静态资源不走controller -->
<mvc:resources mapping="/resources/**" location="/static/"/>
<!-- 配置消息转换器支持接受 application/json;charset=UTF-8 格式请求 -->
<mvc:annotation-driven>
<!--设置不使用默认的消息转换器-->
<mvc:message-converters register-defaults="false">
<!--配置spring的转换器-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
<!--配置fastjson中实现HttpMessageConverter接口的转换器-->
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!--加入支持的媒体类型,返回contentType-->
<property name="supportedMediaTypes">
<list>
<!--这里顺序不能反,一定要先写text/html,不然IE下会出现下载提示-->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
Spring MVC 将参数进行反序列化过程
- Spring MVC 将参数进行反序列化的入口类是
AbstractMessageConverterMethodArgumentResolver
它主要是实现方法参数的解析
下面是我一个测试接口代码:
("/api")
public class TestController {
("/test")
public TestDto test( TestDto dto) {
return new TestDto();
}
("/")
public String test1() {
return "hello spring mvc";
}
}
- 我发起
/api/test
请求, 然后会进入AbstractMessageConverterMethodArgumentResolver
进行方法参数解析
- 上面的
this.messageConverters
就是我们配置的消息转换器,如果我们是 json 个格式将来到下面的代码。
if (converter.canRead(targetClass, contentType)) { // 判断是否能转换成功
if (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
if (inputMessage.getBody() != null) {
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
}
break;
}
// FastJsonHttpMessageConverter 判断是否能解析方法
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return super.canRead(contextClass, mediaType);
}
// AbstractHttpMessageConverter
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return this.supports(clazz) && this.canRead(mediaType);
}
protected boolean canRead(MediaType mediaType) {
if (mediaType == null) {
return true;
} else {
Iterator var2 = this.getSupportedMediaTypes().iterator();
MediaType supportedMediaType;
do {
if (!var2.hasNext()) {
return false;
}
supportedMediaType = (MediaType)var2.next();
} while(!supportedMediaType.includes(mediaType));
return true;
}
}
这里需要注意的是我们配置的消息转换器支持的消息格式 supportedMediaTypes
是我们在 spring-xml
中配置 <value>application/json;charset=UTF-8</value>
。 如果能解析成功就可以进入消息转换器,如果不能解析那么就继续遍历其它的消息转换器。
- 如果不能转换成功返回
HttpMediaTypeNotSupportedException
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && inputMessage.getBody() == null)) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
Spring MVC 请求处理
// HttpServlet
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);
...
doPost
...
doHead
// FrameworkServlet doGet、doPost。。。实现统一调用processRequest
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
// processRequest的实现
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//previousLocaleContext获取和当前线程相关的LocaleContext,根据已有请求构造一个新的和当前线程相关的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
//previousAttributes获取和当前线程绑定的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//为已有请求构造新的ServletRequestAttributes,加入预绑定属性
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);//异步请求处理
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//initContextHolders让新构造的RequestAttributes和ServletRequestAttributes和当前线程绑定,加入到ThreadLocal,完成绑定
initContextHolders(request, localeContext, requestAttributes);
try {
//抽象方法doService由FrameworkServlet子类DispatcherServlet重写
doService(request, response);
}catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}finally {
//解除RequestAttributes,ServletRequestAttributes和当前线程的绑定
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
//注册监听事件ServletRequestHandledEvent,在调用上下文的时候产生Event
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
// DispatcherServlet
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();//保存request域中的数据,存一份快照
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//设置web应用上下文
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());
//请求刷新时保存属性
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
//Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
//FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 FlashMap 实体
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
doDispatch(request, response);//核心方法
}finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);//将快照覆盖回去
}
}
}
}
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 {
//将request转换成multipartRequest,并检查是否解析成功(判断是否有文件上传)
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//根据请求信息获取handler(包含了拦截器)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//根据handler获取adapter
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;
}
}
//拦截器逻辑
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行业务处理,返回视图模型
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//给视图模型设置viewName
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);
}
//处理请求结果,使用了组件LocaleResolver, ViewResolver和ThemeResolver(view#render)
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);
}
}
}
}