Spring MVC 消息转换器

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 本文中主要说明 Spring MVC 消息转换器的选择,也是记录我在工作中遇到的问题。我的 spring mvc 版本是 4.3.10遇到的问题:处理问题是如果 spring mvc 无法接受 json 请求,如果我传 json 格式的参数过去就返回 415 消息格式异常

Spring MVC 消息解析源码


spring-mvc.xml 配置


<?xml version="1.0" encoding="UTF-8"?>
<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 将参数进行反序列化过程


  1. Spring MVC 将参数进行反序列化的入口类是 AbstractMessageConverterMethodArgumentResolver 它主要是实现方法参数的解析


下面是我一个测试接口代码:


@Controller
@RequestMapping("/api")
public class TestController {
    @PostMapping("/test")
    @ResponseBody
    public TestDto test(@RequestBody TestDto dto) {
        return new TestDto();
    }
    @GetMapping("/")
    public String test1() {
        return "hello spring mvc";
    }
}


  1. 我发起 /api/test 请求, 然后会进入 AbstractMessageConverterMethodArgumentResolver 进行方法参数解析


image.png


  1. 上面的 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> 。 如果能解析成功就可以进入消息转换器,如果不能解析那么就继续遍历其它的消息转换器。


  1. 如果不能转换成功返回 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);
            }
        }
    }
}



相关文章
|
19天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
6天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
2月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
2月前
|
XML JSON 数据库
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
这篇文章详细介绍了RESTful的概念、实现方式,以及如何在SpringMVC中使用HiddenHttpMethodFilter来处理PUT和DELETE请求,并通过具体代码案例分析了RESTful的使用。
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
|
2月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
2月前
|
JSON 前端开发 Java
Spring MVC返回JSON数据
综上所述,Spring MVC提供了灵活、强大的方式来支持返回JSON数据,从直接使用 `@ResponseBody`及 `@RestController`注解,到通过配置消息转换器和异常处理器,开发人员可以根据具体需求选择合适的实现方式。
94 4
|
2月前
|
XML 前端开发 Java
Spring MVC接收param参数(直接接收、注解接收、集合接收、实体接收)
Spring MVC提供了灵活多样的参数接收方式,可以满足各种不同场景下的需求。了解并熟练运用这些基本的参数接收技巧,可以使得Web应用的开发更加方便、高效。同时,也是提高代码的可读性和维护性的关键所在。在实际开发过程中,根据具体需求选择最合适的参数接收方式,能够有效提升开发效率和应用性能。
88 3
|
2月前
|
XML 前端开发 Java
Spring MVC接收param参数(直接接收、注解接收、集合接收、实体接收)
Spring MVC提供了灵活多样的参数接收方式,可以满足各种不同场景下的需求。了解并熟练运用这些基本的参数接收技巧,可以使得Web应用的开发更加方便、高效。同时,也是提高代码的可读性和维护性的关键所在。在实际开发过程中,根据具体需求选择最合适的参数接收方式,能够有效提升开发效率和应用性能。
89 2
|
2月前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
48 0
|
3月前
|
JSON 前端开发 Java
Spring Boot中的MVC支持
本节课主要讲解了 Spring Boot 中对 MVC 的支持,分析了 @RestController、 @RequestMapping、@PathVariable、 @RequestParam 和 @RequestBody 四个注解的使用方式,由于 @RestController 中集成了 @ResponseBody 所以对返回 json 的注解不再赘述。以上四个注解是使用频率很高的注解,在所有的实际项目中基本都会遇到,要熟练掌握。
下一篇
无影云桌面