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
- The entry class for Spring MVC to deserialize arguments is
AbstractMessageConverterMethodArgumentResolver
It 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
- I am a
/api/test
Request, and then enterAbstractMessageConverterMethodArgumentResolver
Perform method parameter parsing
- The above
this.messageConverters
Is 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.
- Returns if the conversion failed
HttpMediaTypeNotSupportedException
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