An overview of the

This article mainly explains the choice of Spring MVC message converter, but also records the problems I encountered in the work.

My Spring MVC version is 4.3.10

Problem encountered: The processing problem is that if Spring MVC cannot accept a JSON request, if I pass a JSON format argument, I return 415 message format exception

Spring MVC message parsing source code

Spring – the MVC. XML configuration


      
<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/>
    <! -- Scan the web package and apply Spring annotations -->
    <context:component-scan base-package="cn.edu.cqvie.mvc.controller"/>


    <! -- JSP view -->
    <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>

    <! -- Static resources don't go controller -->
    <mvc:resources mapping="/resources/**" location="/static/"/>

    <! -- Configure the message converter to accept Application/JSON; Charset =UTF-8 -->
    <mvc:annotation-driven>
        <! -- Set not to use the default message converter -->
        <mvc:message-converters register-defaults="false">
            <! -- Configure spring converter -->
            <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"/>
            <! HttpMessageConverter -->
            <bean id="fastJsonHttpMessageConverter"
                  class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <! Add supported media types, return contentType-->
                <property name="supportedMediaTypes">
                    <list>
                        <! If you want to write a text/ HTML file, you can download it from Internet Explorer.
                        <value>text/html; charset=UTF-8</value>
                        <value>application/json; charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>
Copy the code

Spring MVC deserializes the parameters

  1. The entry class for Spring MVC to deserialize arguments isAbstractMessageConverterMethodArgumentResolverIt mainly implements the resolution of method parameters

Here is my test interface code:

@Controller
@RequestMapping("/api")
public class TestController {

    @PostMapping("/test")
    @ResponseBody
    public TestDto test(@RequestBody TestDto dto) {
        return new TestDto();
    }

    @GetMapping("/")
    public String test1(a) {
        return "hello spring mvc"; }}Copy the code
  1. I am a/api/testRequest, and then enterAbstractMessageConverterMethodArgumentResolverPerform method parameter parsing

  1. The abovethis.messageConvertersIs the message converter we configured, if we are in JSON format will come to the following code.
if (converter.canRead(targetClass, contentType)) { // Determine whether the conversion succeeds
  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 can determine whether analytical method
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; }}Copy the code

Note here that the message converter we configured supports the message format supportedMediaTypes where we configured

application/json in spring-XML; Charset = utf-8 < value >. If it parses successfully, it enters the message converter, if not, it continues through the other message converters.

  1. Returns if the conversion failedHttpMediaTypeNotSupportedException
if (body == NO_VALUE) {
    if (httpMethod == null| |! SUPPORTED_METHODS.contains(httpMethod) || (noContentType && inputMessage.getBody() ==null)) {
        return null;
    }
    throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
Copy the code

Spring MVC request handling

// 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 logicdoGet(req, resp); . doPost ... doHead// FrameworkServlet doGet, doPost... Implement a unified call to processRequest
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
   processRequest(request, response);
}                

            
// Implementation of processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    //previousLocaleContext gets the LocaleContext associated with the current thread and constructs a new LocaleContext associated with the current thread based on the existing request
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    PreviousAttributes Gets the RequestAttributes bound to the current thread
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    // Construct new ServletRequestAttributes for existing requests, adding pre-bound attributes
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);// Asynchronous request processing
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    //initContextHolders bind newly constructed RequestAttributes and ServletRequestAttributes to ThreadLocal
    initContextHolders(request, localeContext, requestAttributes);

    try {
        // The abstract method doService is overridden by the FrameworkServlet subclass 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 {
        / / remove RequestAttributes, ServletRequestAttributes and binding of the current thread
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if(requestAttributes ! =null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        / / registered ServletRequestHandledEvent listen Event, the Event at the time of invocation contextpublishRequestHandledEvent(request, response, startTime, failureCause); }}Copy the code
// 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<>();// Save a snapshot of the data in the request fieldEnumeration<? > 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)); }}}// Set the Web application context
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    // Internationalize locally
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    / / style
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    // Set the style resource
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    // Save properties when requested to refresh
    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 are stored temporarily (usually in session) until a redirect to the request takes effect, and removed immediately after the redirect
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        //FlashMap is used to manage flash Attributes while FlashMapManager is used to store, retrieve, and manage FlashMap entities
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }
    try {
        doDispatch(request, response);// Core method
    }finally {
        if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.
            if(attributesSnapshot ! =null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);// Overwrite the snapshot}}}}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 {
            // Convert request to multipartRequest and check if it was successfully parsed.processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);// Get handler based on request information (including interceptor)
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // Obtain the adapter from handler
            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; }}// Interceptor logic
            if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
            }
            // Perform business processing and return to the viewmodel
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            // Set the viewName for the viewmodel
            applyDefaultViewName(processedRequest, mv);
            // Interceptor logic
            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);
        }
        // handle the result of the request using components LocaleResolver, ViewResolver and 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); }}}}Copy the code