Customize Spring Boot content negotiation

The content negotiation mechanism means that the client and the server negotiate the content of the resource in response, and then provide the client with the most suitable resource. The content negotiation will use the language, character set, and encoding method of the response resource as the basis for judgment. Content-Type, Accept and other contents in the HTTP request header are the criteria for content negotiation and judgment. In Spring Boot, a complete content negotiation process is shown in the following figure:

 

The core components of this process:

Component name Description
ContentNegotiationManager Content Negotiation Manager ContentNegotiationStrategy control strategy
MediaType media type HTTP message media type, such as text/html
@RequestMapping#consumes Consumer media type Request header Content-Type media type mapping
@RequestMapping#produces Production media type Response header Content-Type media type mapping
HttpMessageConverter HTTP message converter interface HTTP message converter, used to deserialize HTTP requests or serialize responses
WebMvcConfigurer Web MVC Configurator Configure REST related components
HandlerMethod Approach @RequestMapping annotated method
HandlerMethodArgumentResolver Processing method parameter parser Used to parse the HandlerMethod parameter content in HTTP requests
HandlerMethodReturnValueHandler Processing method return value parser Used to parse HandlerMethod return value into HTTP response content

HttpMessageConverterFor the HTTP message conversion interface, Spring implements the corresponding implementation according to different media types. For example, in the above figure, Accept is application/json, so in step 7, HttpMessageConverterthe implementation class MappingJackson2HttpMessageConverterused will be selected to process the return value.

Custom HttpMessageConverter

In addition to the HttpMessageConverterimplementation provided by Spring , we can also customize HttpMessageConverterthe implementation to handle some actual business needs.

If we now want to implement an HttpMessageConverter implementation class PropertiesHttpMessageConverter that is used to process the media type whose Content-Type is text/properties, when we transmit the following content in the request body:

name:mrbrid
age:18

Can be automatically converted to Properties object.

We can MappingJackson2HttpMessageConverterimplement it with reference to the implementation method, and view MappingJackson2HttpMessageConverterthe prototype diagram:

So we can AbstractGenericHttpMessageConverterimplement the HttpMessageConverterinterface through inheritance .

Create a new Spring Boot project, the version is 2.1.0.RELEASE, and introduce spring-boot-starter-webdependencies, the project structure is as follows:

We com.example.democreate a new converterpackage under the path , then create PropertiesHttpMessageConverterand inherit AbstractGenericHttpMessageConverter:

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {
    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    }
    @Override
    protected Properties readInternal(Class<? extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }
    @Override
    public Properties read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }
}

Among them readxxxis the deserialization process, that is, the process of deserializing the HTTP request into parameters; writeInternalis the serialization process, which serializes the response.

Deserialization process

We continue to write PropertiesHttpMessageConverter:

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    }

    @Override
    protected Properties readInternal(Class<? extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        Properties properties = new Properties();
        // 获取请求头
        HttpHeaders headers = inputMessage.getHeaders();
        // 获取 content-type
        MediaType contentType = headers.getContentType();
        // 获取编码
        Charset charset = null;
        if (contentType != null) {
            charset = contentType.getCharset();
        }

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        // 获取请求体
        InputStream body = inputMessage.getBody();
        InputStreamReader inputStreamReader = new InputStreamReader(body, charset);
        
        properties.load(inputStreamReader);
        return properties;
    }

    @Override
    public Properties read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return readInternal(null, inputMessage);
    }
}

In the readInternalmethod, we get the input stream and encoding in the HTTP request body, and then call the loadmethod of the Properties object to convert the stream into a Properties object. After the deserialization process is complete, we still need to PropertiesHttpMessageConverteradd it to the HttpMessageConvertercollection.

com.example.demoCreate a new configpackage under the path , and then create a WebConfigurerconfiguration class:

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new PropertiesHttpMessageConverter());
    }
}

extendMessageConvertersThe method is WebMvcConfigurerthe default method, here we rewrite this method to PropertiesHttpMessageConverteradd to the message converter collection.

Then create a Controller to test a wave, create a com.example.demonew controllerpackage under the path , and then create TestController:

@RestController
public class TestController {

    @GetMapping(value = "test", consumes = "text/properties")
    public Properties test(@RequestBody Properties properties) {
        return properties;
    }
}

We specify the media type received by the method through the @GetMappingannotated consumesattribute text/properties. If the method can be successfully called and can return an Propertiesobject, it means that our custom HTTP message converter is feasible.

Start the project and use PostMan to access:

The Content-Type is specified as text/properties in the request header, and the content of the request body is as follows:

After access, the console output error is as follows:

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.util.Properties com.example.demo.controller.TestController.test(java.util.Properties)]

why? Because we PropertiesHttpMessageConverterneed to specify the media type that it can handle in its constructor when we customize it, let's check MappingJackson2HttpMessageConverterthe constructor to see how it is implemented:

So we PropertiesHttpMessageConverteradd the corresponding media type in the constructor:

public PropertiesHttpMessageConverter() {
    super(new MediaType("text", "properties"));
}

At this time, restart the project and visit the above request again, you can see the response as shown below:

Serialization process

The process is the sequence of processing of an HTTP response, corresponding to PropertiesHttpMessageConverterthe writeInternalmethod. So why we haven't implemented this method yet, but the above Controller request can return normal JSON content? Two reasons:

  1. Here we define the REST interface, so the response will be serialized into JSON format by default;

  2. Since converters.add(new PropertiesHttpMessageConverter());this method is used to add a custom HTTP message handler, it will be added to the end of the collection by default, and it is ranked first when processing JSON responses MappingJackson2HttpMessageConverter.

We can use debug to check PropertiesHttpMessageConverterwhether it is actually added to the end of the collection:

So we have to change the following way to add a custom HTTP handler:

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // converters.add(new PropertiesHttpMessageConverter());
        // 指定顺序,这里为第一个
        converters.add(0, new PropertiesHttpMessageConverter());
    }
}

We restart the project and debug again:

As you can see, it is PropertiesHttpMessageConverteralready ranked first. At this time, visit the above request again, and the response is as follows:

There is no return value, this is because we haven't implemented writeInternalit yet. Continue to achieve the writeInternalmethod:

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

    ...

    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // 获取请求头
        HttpHeaders headers = outputMessage.getHeaders();
        // 获取 content-type
        MediaType contentType = headers.getContentType();
        // 获取编码
        Charset charset = null;
        if (contentType != null) {
            charset = contentType.getCharset();
        }

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        // 获取请求体
        OutputStream body = outputMessage.getBody();
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(body, charset);

        properties.store(outputStreamWriter, "Serialized by PropertiesHttpMessageConverter#writeInternal");
    }

    ...
}

The process is similar to deserialization, here is the write operation through Propertiesthe storemethod of the object .

Restart the project, visit the above request again, and the response is as follows:

Custom HandlerMethodArgumentResolver

The above method must rely on @RequestBodyand @ResponseBodyannotations. In addition, we can also handle content negotiation by customizing HandlerMethodArgumentResolverand HandlerMethodReturnValueHandlerimplementing classes.

HandlerMethodArgumentResolverCommonly known as the method parameter parser, it is used to parse @RequestMappingthe parameters of the method marked by the annotation (or its derivative annotations). Here we begin HandlerMethodArgumentResolverto automatically parse the content of the HTTP request body into a Properties object by way of implementation .

com.example.demoCreate a new resolverpackage under the path , and then create PropertiesHandlerMethodArgumentResolver

Implement the HandlerMethodArgumentResolverinterface:

public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Properties.class.equals(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
        HttpServletRequest request = servletWebRequest.getRequest();
        String contentType = request.getHeader("Content-Type");

        MediaType mediaType = MediaType.parseMediaType(contentType);
        // 获取编码
        Charset charset = mediaType.getCharset() == null ? Charset.forName("UTF-8") : mediaType.getCharset();
        // 获取输入流
        InputStream inputStream = request.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);

        // 输入流转换为 Properties
        Properties properties = new Properties();
        properties.load(inputStreamReader);
        return properties;
    }
}

The method is supportsParameterused to specify the parameter types that support parsing, here is the Properties type. resolveArgumentThe method is used to implement the parsing logic, and the parsing process is similar to PropertiesHttpMessageConverterthe readInternalmethod defined above .

Next, we need to PropertiesHandlerMethodArgumentResolveradd it to the HandlerMethodArgumentResolvercollection of implementation classes that comes with Spring . It is worth noting that we can not add WebMvcConfigurerby rewriting in the configuration class addArgumentResolvers, check the comments on the source code of the method:

The general meaning is that the method parameter parser added through this method will not cover the Spring's built-in method parameter parser. If you need to do this, you can directly modify RequestMappingHandlerAdapterit.

So we can achieve this in the following way:

@Configuration
public class WebConfigurer implements WebMvcConfigurer {


    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        // 获取当前 RequestMappingHandlerAdapter 所有的 ArgumentResolver对象
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        List<HandlerMethodArgumentResolver> newArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1);
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合第一个位置
        newArgumentResolvers.add(0, new PropertiesHandlerMethodArgumentResolver());
        // 将原 ArgumentResolver 添加到集合中
        newArgumentResolvers.addAll(argumentResolvers);
        // 重新设置 ArgumentResolver对象集合
        requestMappingHandlerAdapter.setArgumentResolvers(newArgumentResolvers);
    }
}

When the WebConfigurerconfiguration class is assembled, we reset the method parser collection through requestMappingHandlerAdapterthe setArgumentResolversmethod of the object and will be PropertiesHandlerMethodArgumentResolveradded to the first position of the collection.

The reason to PropertiesHandlerMethodArgumentResolveradd it to the first position is because Properties is essentially a Map object, and Spring is built-in MapMethodProcessorto handle Map parameter types. If the PropertiesHandlerMethodArgumentResolverpriority is not increased, then the Properties type parameters will be MapMethodProcessorparsed and errors will occur.

After the configuration is complete, let's transform it TestController:

// @RestController
@Controller
public class TestController {

    @GetMapping(value = "test", consumes = "text/properties")
    @ResponseBody
    public Properties test(@RequestBody Properties properties) {
        return properties;
    }

    @GetMapping(value = "test1", consumes = "text/properties")
    @ResponseBody
    public Properties test1(Properties properties) {
        return properties;
    }
}

test1The parameters of the method are not @RequestBodymarked, start the project, and visit the following request:

You can see that the method was successfully executed and returned the correct content, indicating that our custom method parameter parser PropertiesHandlerMethodArgumentResolveris feasible.

But the return value of PropertiesHttpMessageConverterthe writeInternalmethod is still parsed by the method and depends on the @ResponseBodyannotation, and then we start to implement a custom method return value parser, and does not depend on the @ResponseBodyannotation.

Custom HandlerMethodReturnValueHandler

HandlerMethodArgumentResolverCommonly known as the method return value parser, it is used to parse @RequestMappingthe return value of the method marked by the annotation (or its derivative annotations). Here we start to HandlerMethodReturnValueHandlercustomize a parser for processing the return value type of Properties type by way of implementation .

com.example.demoCreate a new handlerpackage under the path , and then create an PropertiesHandlerMethodReturnValueHandlerimplementation HandlerMethodReturnValueHandler:

public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return Properties.class.equals(returnType.getMethod().getReturnType());
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        Properties properties = (Properties) returnValue;

        ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;

        HttpServletResponse response = servletWebRequest.getResponse();
        ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);

        // 获取请求头
        HttpHeaders headers = servletServerHttpResponse.getHeaders();

        MediaType contentType = headers.getContentType();
        // 获取编码
        Charset charset = null;
        if (contentType != null) {
            charset = contentType.getCharset();
        }

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        // 获取请求体
        OutputStream body = servletServerHttpResponse.getBody();
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(body, charset);

        properties.store(outputStreamWriter, "Serialized by PropertiesHandlerMethodReturnValueHandler#handleReturnValue");
    }
}

supportsReturnTypeThe method of processing is designated type of return value, handleReturnValuea method for processing a return value, where the logic and PropertiesHttpMessageConverterthe writeInternalmethod are basically the same, it is omitted.

Then it will be PropertiesHandlerMethodReturnValueHandleradded to the HandlerMethodReturnValueHandlercollection of implementation classes that comes with Spring , and the addition method is the HandlerMethodArgumentResolversame as the customization :

@Configuration
public class WebConfigurer implements WebMvcConfigurer {


    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        // 获取当前 RequestMappingHandlerAdapter 所有的 ArgumentResolver对象
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        List<HandlerMethodArgumentResolver> newArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1);
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合第一个位置
        newArgumentResolvers.add(0, new PropertiesHandlerMethodArgumentResolver());
        // 将原 ArgumentResolver 添加到集合中
        newArgumentResolvers.addAll(argumentResolvers);
        // 重新设置 ArgumentResolver对象集合
        requestMappingHandlerAdapter.setArgumentResolvers(newArgumentResolvers);

        // 获取当前 RequestMappingHandlerAdapter 所有的 returnValueHandlers对象
        List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newReturnValueHandlers = new ArrayList<>(returnValueHandlers.size() + 1);
        // 添加 PropertiesHandlerMethodReturnValueHandler 到集合第一个位置
        newReturnValueHandlers.add(0, new PropertiesHandlerMethodReturnValueHandler());
        // 将原 returnValueHandlers 添加到集合中
        newReturnValueHandlers.addAll(returnValueHandlers);
        // 重新设置 ReturnValueHandlers对象集合
        requestMappingHandlerAdapter.setReturnValueHandlers(newReturnValueHandlers);
    }
}

After configuration, we remove the annotations TestControllerof the test1method @ResponseBody, restart the project, and visit again:

As you can see, the return value was successfully parsed by PropertiesHandlerMethodReturnValueHandlerthe handleReturnValuemethod.

But there is another problem here. When we check the console, we will find the following exception:

javax.servlet.ServletException: Circular view path [test1]: would dispatch back to the current handler URL [/test1] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
	at org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:209) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:147) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1370) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1116) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:998) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:890) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:875) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) ~[tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_171]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_171]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.12.jar:9.0.12]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]

This is because, in Spring, if the method in the Controller is not @ResponseBodymarked, the return value will be regarded as the name of the view by default, and here we do not want the resolved Properties value to be regarded as the name of the view, so we need to be in PropertiesHandlerMethodReturnValueHandlerthe handleReturnValuelast line of the method Add the following code:

// 告诉 Spring MVC 请求已经处理完毕
mavContainer.setRequestHandled(true);

This line of code tells Spring that the request has been successfully completed and no further processing is required. Restart the project and visit the above request again, and the console will no longer throw exceptions.

 

 

Guess you like

Origin blog.csdn.net/u014225733/article/details/100858053