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 |
HttpMessageConverter
For 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, HttpMessageConverter
the implementation class MappingJackson2HttpMessageConverter
used will be selected to process the return value.
Custom HttpMessageConverter
In addition to the HttpMessageConverter
implementation provided by Spring , we can also customize HttpMessageConverter
the 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 MappingJackson2HttpMessageConverter
implement it with reference to the implementation method, and view MappingJackson2HttpMessageConverter
the prototype diagram:
So we can AbstractGenericHttpMessageConverter
implement the HttpMessageConverter
interface through inheritance .
Create a new Spring Boot project, the version is 2.1.0.RELEASE, and introduce spring-boot-starter-web
dependencies, the project structure is as follows:
We com.example.demo
create a new converter
package under the path , then create PropertiesHttpMessageConverter
and 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 readxxx
is the deserialization process, that is, the process of deserializing the HTTP request into parameters; writeInternal
is 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 readInternal
method, we get the input stream and encoding in the HTTP request body, and then call the load
method of the Properties object to convert the stream into a Properties object. After the deserialization process is complete, we still need to PropertiesHttpMessageConverter
add it to the HttpMessageConverter
collection.
com.example.demo
Create a new config
package under the path , and then create a WebConfigurer
configuration class:
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PropertiesHttpMessageConverter());
}
}
extendMessageConverters
The method is WebMvcConfigurer
the default method, here we rewrite this method to PropertiesHttpMessageConverter
add to the message converter collection.
Then create a Controller to test a wave, create a com.example.demo
new controller
package 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 @GetMapping
annotated consumes
attribute text/properties
. If the method can be successfully called and can return an Properties
object, 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 PropertiesHttpMessageConverter
need to specify the media type that it can handle in its constructor when we customize it, let's check MappingJackson2HttpMessageConverter
the constructor to see how it is implemented:
So we PropertiesHttpMessageConverter
add 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 PropertiesHttpMessageConverter
the writeInternal
method. So why we haven't implemented this method yet, but the above Controller request can return normal JSON content? Two reasons:
-
Here we define the REST interface, so the response will be serialized into JSON format by default;
-
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 responsesMappingJackson2HttpMessageConverter
.
We can use debug to check PropertiesHttpMessageConverter
whether 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 PropertiesHttpMessageConverter
already 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 writeInternal
it yet. Continue to achieve the writeInternal
method:
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 Properties
the store
method 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 @RequestBody
and @ResponseBody
annotations. In addition, we can also handle content negotiation by customizing HandlerMethodArgumentResolver
and HandlerMethodReturnValueHandler
implementing classes.
HandlerMethodArgumentResolver
Commonly known as the method parameter parser, it is used to parse @RequestMapping
the parameters of the method marked by the annotation (or its derivative annotations). Here we begin HandlerMethodArgumentResolver
to automatically parse the content of the HTTP request body into a Properties object by way of implementation .
com.example.demo
Create a new resolver
package under the path , and then create PropertiesHandlerMethodArgumentResolver
Implement the HandlerMethodArgumentResolver
interface:
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 supportsParameter
used to specify the parameter types that support parsing, here is the Properties type. resolveArgument
The method is used to implement the parsing logic, and the parsing process is similar to PropertiesHttpMessageConverter
the readInternal
method defined above .
Next, we need to PropertiesHandlerMethodArgumentResolver
add it to the HandlerMethodArgumentResolver
collection of implementation classes that comes with Spring . It is worth noting that we can not add WebMvcConfigurer
by 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 RequestMappingHandlerAdapter
it.
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 WebConfigurer
configuration class is assembled, we reset the method parser collection through requestMappingHandlerAdapter
the setArgumentResolvers
method of the object and will be PropertiesHandlerMethodArgumentResolver
added to the first position of the collection.
The reason to PropertiesHandlerMethodArgumentResolver
add it to the first position is because Properties is essentially a Map object, and Spring is built-in MapMethodProcessor
to handle Map parameter types. If the PropertiesHandlerMethodArgumentResolver
priority is not increased, then the Properties type parameters will be MapMethodProcessor
parsed 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;
}
}
test1
The parameters of the method are not @RequestBody
marked, 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 PropertiesHandlerMethodArgumentResolver
is feasible.
But the return value of PropertiesHttpMessageConverter
the writeInternal
method is still parsed by the method and depends on the @ResponseBody
annotation, and then we start to implement a custom method return value parser, and does not depend on the @ResponseBody
annotation.
Custom HandlerMethodReturnValueHandler
HandlerMethodArgumentResolver
Commonly known as the method return value parser, it is used to parse @RequestMapping
the return value of the method marked by the annotation (or its derivative annotations). Here we start to HandlerMethodReturnValueHandler
customize a parser for processing the return value type of Properties type by way of implementation .
com.example.demo
Create a new handler
package under the path , and then create an PropertiesHandlerMethodReturnValueHandler
implementation 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");
}
}
supportsReturnType
The method of processing is designated type of return value, handleReturnValue
a method for processing a return value, where the logic and PropertiesHttpMessageConverter
the writeInternal
method are basically the same, it is omitted.
Then it will be PropertiesHandlerMethodReturnValueHandler
added to the HandlerMethodReturnValueHandler
collection of implementation classes that comes with Spring , and the addition method is the HandlerMethodArgumentResolver
same 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 TestController
of the test1
method @ResponseBody
, restart the project, and visit again:
As you can see, the return value was successfully parsed by PropertiesHandlerMethodReturnValueHandler
the handleReturnValue
method.
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 @ResponseBody
marked, 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 PropertiesHandlerMethodReturnValueHandler
the handleReturnValue
last 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.