O mecanismo de negociação de conteúdo significa que o cliente e o servidor negociam o conteúdo do recurso em resposta e, em seguida, fornecem ao cliente o recurso mais adequado. A negociação de conteúdo usará o idioma, o conjunto de caracteres e o método de codificação do recurso de resposta como base para o julgamento. Content-Type, Accept e outros conteúdos no cabeçalho de solicitação HTTP são os critérios para negociação e julgamento de conteúdo. No Spring Boot, um processo completo de negociação de conteúdo é mostrado na figura a seguir:
Os principais componentes deste processo:
Componente | nome | Descrição |
---|---|---|
ContentNegotiationManager | Gerente de negociação de conteúdo | ContentNegotiationStrategy control strategy |
Tipo de mídia | tipo de mídia | Tipo de mídia de mensagem HTTP, como text / html |
@ RequestMapping # consome | Tipo de mídia do consumidor | Solicitar mapeamento de tipo de mídia de tipo de conteúdo de cabeçalho |
@ RequestMapping # produz | Tipo de mídia de produção | Mapeamento de tipo de mídia de tipo de conteúdo de cabeçalho de resposta |
HttpMessageConverter | Interface do conversor de mensagem HTTP | Conversor de mensagem HTTP, usado para desserializar solicitações HTTP ou serializar respostas |
WebMvcConfigurer | Configurador Web MVC | Configurar componentes relacionados ao REST |
HandlerMethod | Aproximação | Método anotado @RequestMapping |
HandlerMethodArgumentResolver | Analisador de parâmetros do método de processamento | Usado para analisar o conteúdo do parâmetro HandlerMethod em solicitações HTTP |
HandlerMethodReturnValueHandler | Analisador de valor de retorno do método de processamento | Usado para analisar o valor de retorno de HandlerMethod no conteúdo de resposta HTTP |
HttpMessageConverter
Para a interface de conversão de mensagem HTTP, Spring implementa a implementação correspondente de acordo com diferentes tipos de mídia. Por exemplo, na figura acima, Aceitar é application / json, portanto, na etapa 7, HttpMessageConverter
a classe de implementação MappingJackson2HttpMessageConverter
usada será selecionada para processar o valor de retorno.
HttpMessageConverter personalizado
Além da HttpMessageConverter
implementação fornecida pelo Spring , também podemos personalizar HttpMessageConverter
a implementação para lidar com algumas necessidades de negócios reais.
Se agora queremos implementar uma classe de implementação HttpMessageConverter PropertiesHttpMessageConverter que é usada para processar o tipo de mídia cujo Content-Type é texto / propriedades, quando transmitimos o seguinte conteúdo no corpo da solicitação:
name:mrbrid
age:18
Pode ser convertido automaticamente para o objeto Propriedades.
Podemos MappingJackson2HttpMessageConverter
implementá- lo com referência ao método de implementação e visualizar MappingJackson2HttpMessageConverter
o diagrama do protótipo:
Portanto, podemos AbstractGenericHttpMessageConverter
implementar a HttpMessageConverter
interface por meio de herança .
Crie um novo projeto Spring Boot, a versão é 2.1.0.RELEASE, e introduza spring-boot-starter-web
dependências, a estrutura do projeto é a seguinte:
Criamos com.example.demo
um novo converter
pacote no caminho , em seguida, criamos PropertiesHttpMessageConverter
e herdamos 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;
}
}
Entre eles readxxx
está o processo de desserialização, ou seja, o processo de desserialização da solicitação HTTP em parâmetros, writeInternal
é o processo de serialização, que serializa a resposta.
Processo de desserialização
Continuamos a escrever 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);
}
}
No readInternal
método, obtemos o fluxo de entrada e a codificação no corpo da solicitação HTTP e, em seguida, chamamos o load
método do objeto Propriedades para converter o fluxo em um objeto Propriedades. Após a conclusão do processo de desserialização, ainda precisamos PropertiesHttpMessageConverter
adicioná-lo à HttpMessageConverter
coleção.
com.example.demo
Crie um novo config
pacote no caminho e, em seguida, crie uma WebConfigurer
classe de configuração:
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PropertiesHttpMessageConverter());
}
}
extendMessageConverters
O método é WebMvcConfigurer
o método padrão, aqui nós reescrevemos esse método para PropertiesHttpMessageConverter
adicioná-lo à coleção do conversor de mensagem.
Em seguida, crie um controlador para testar uma onda, crie um com.example.demo
novo controller
pacote sob o caminho e, em seguida, crie TestController
:
@RestController
public class TestController {
@GetMapping(value = "test", consumes = "text/properties")
public Properties test(@RequestBody Properties properties) {
return properties;
}
}
Especificamos o tipo de mídia recebido pelo método por meio do atributo @GetMapping
anotado . Se o método pode ser chamado com sucesso e pode retornar um objeto, significa que nosso conversor de mensagem HTTP personalizado é viável.consumes
text/properties
Properties
Inicie o projeto e use o PostMan para acessar:
O Content-Type é especificado como texto / propriedades no cabeçalho da solicitação e o conteúdo do corpo da solicitação é o seguinte:
Após o acesso, o erro de saída do console é o seguinte:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.util.Properties com.example.demo.controller.TestController.test(java.util.Properties)]
porque? Como PropertiesHttpMessageConverter
precisamos especificar o tipo de mídia que ele pode manipular em seu construtor quando o personalizamos , vamos verificar MappingJackson2HttpMessageConverter
o construtor para ver como ele é implementado:
Portanto, PropertiesHttpMessageConverter
adicionamos o tipo de mídia correspondente no construtor:
public PropertiesHttpMessageConverter() {
super(new MediaType("text", "properties"));
}
Neste momento, reinicie o projeto e visite a solicitação acima novamente, você pode ver a resposta conforme mostrado abaixo:
Processo de serialização
O processo é a sequência de processamento de uma resposta de HTTP, correspondente ao PropertiesHttpMessageConverter
do writeInternal
método. Então, por que ainda não implementamos esse método, mas a solicitação do controlador acima pode retornar conteúdo JSON normal? Duas razões:
-
Aqui definimos a interface REST, portanto, a resposta será serializada no formato JSON por padrão;
-
Como
converters.add(new PropertiesHttpMessageConverter());
esse método é usado para adicionar um manipulador de mensagem HTTP customizado, ele será adicionado ao final da coleção por padrão e será classificado em primeiro lugar ao processar as respostas JSONMappingJackson2HttpMessageConverter
.
Podemos usar a depuração para verificar PropertiesHttpMessageConverter
se ele é realmente adicionado ao final da coleção:
Portanto, temos que alterar a seguinte maneira de adicionar um manipulador HTTP personalizado:
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// converters.add(new PropertiesHttpMessageConverter());
// 指定顺序,这里为第一个
converters.add(0, new PropertiesHttpMessageConverter());
}
}
Reiniciamos o projeto e depuramos novamente:
Como você pode ver, ele PropertiesHttpMessageConverter
já está classificado em primeiro lugar. Nesse momento, visite a solicitação acima novamente e a resposta é a seguinte:
Não há valor de retorno, isso é porque ainda não o implementamos writeInternal
. Continue a alcançar o writeInternal
método:
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");
}
...
}
O processo é semelhante à desserialização, aqui está a operação de gravação por meio Properties
do store
método do objeto .
Reinicie o projeto, visite a solicitação acima novamente e a resposta é a seguinte:
Custom HandlerMethodArgumentResolver
O método acima deve confiar em @RequestBody
e @ResponseBody
anotações. Além disso, também podemos lidar com negociação de conteúdo , personalizando HandlerMethodArgumentResolver
e HandlerMethodReturnValueHandler
aulas de execução.
HandlerMethodArgumentResolver
Comumente conhecido como analisador de parâmetros do método, é usado para analisar @RequestMapping
os parâmetros do método marcado pela anotação (ou suas anotações derivadas). Aqui, começamos HandlerMethodArgumentResolver
a analisar automaticamente o conteúdo do corpo da solicitação HTTP em um objeto Propriedades por meio de implementação .
com.example.demo
Crie um novo resolver
pacote no caminho e, em seguida, crie PropertiesHandlerMethodArgumentResolver
Implemente a 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;
}
}
O método é supportsParameter
usado para especificar os tipos de parâmetro que oferecem suporte à análise, aqui está o tipo de Propriedades. resolveArgument
O método é usado para implementar a lógica de análise, e o processo de análise é semelhante ao PropertiesHttpMessageConverter
do readInternal
método definido acima .
Em seguida, precisamos PropertiesHandlerMethodArgumentResolver
adicioná-lo à HandlerMethodArgumentResolver
coleção de classes de implementação que vem com o Spring . É importante ressaltar que não podemos adicionar WebMvcConfigurer
reescrevendo na classe de configuração addArgumentResolvers
, verifique os comentários no código-fonte do método:
O significado geral é que o analisador de parâmetro de método adicionado por meio deste método não cobrirá o analisador de parâmetro de método embutido do Spring. Se você precisar fazer isso, pode modificá- RequestMappingHandlerAdapter
lo diretamente .
Portanto, podemos conseguir isso da seguinte maneira:
@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);
}
}
Quando a WebConfigurer
classe de configuração é montada, redefinimos a coleção do analisador de método por meio requestMappingHandlerAdapter
do setArgumentResolvers
método do objeto e será PropertiesHandlerMethodArgumentResolver
adicionado à primeira posição da coleção.
A razão para PropertiesHandlerMethodArgumentResolver
adicioná-lo à primeira posição é porque Properties é essencialmente um objeto Map, e Spring é integrado MapMethodProcessor
para lidar com os tipos de parâmetro Map. Se a PropertiesHandlerMethodArgumentResolver
prioridade não for aumentada, então os parâmetros do tipo Properties serão MapMethodProcessor
analisados e erros ocorrerão.
Depois que a configuração for concluída, vamos transformá-la 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
Os parâmetros do método não estão @RequestBody
marcados, inicie o projeto e acesse a seguinte solicitação:
Você pode ver que o método foi executado com sucesso e retornou o conteúdo correto, indicando que nosso analisador de parâmetro de método personalizado PropertiesHandlerMethodArgumentResolver
é viável.
Mas o valor de retorno PropertiesHttpMessageConverter
do writeInternal
método ainda é analisado pelo método e depende da @ResponseBody
anotação, e então começamos a implementar um método personalizado analisador valor de retorno, e não dependem da @ResponseBody
anotação.
Custom HandlerMethodReturnValueHandler
HandlerMethodArgumentResolver
Normalmente conhecido como analisador de valor de retorno do método, ele é usado para analisar @RequestMapping
o valor de retorno do método marcado pela anotação (ou suas anotações derivadas). Aqui, começamos a HandlerMethodReturnValueHandler
personalizar um analisador para processar o tipo de valor de retorno do tipo Propriedades por meio de implementação .
com.example.demo
Crie um novo handler
pacote no caminho e, em seguida, crie uma PropertiesHandlerMethodReturnValueHandler
implementação 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
O método de processamento é designado tipo de valor de retorno, handleReturnValue
um método para processar um valor de retorno, onde a lógica e PropertiesHttpMessageConverter
o writeInternal
método são basicamente os mesmos, ele é omitido.
Em seguida, ele será PropertiesHandlerMethodReturnValueHandler
adicionado à HandlerMethodReturnValueHandler
coleção de classes de implementação que vem com o Spring , e o método de adição é o HandlerMethodArgumentResolver
mesmo da personalização :
@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);
}
}
Após a configuração, removemos as anotações TestController
do test1
método @ResponseBody
, reiniciamos o projeto e visitamos novamente:
Como você pode ver, o valor de retorno foi sucesso analisado PropertiesHandlerMethodReturnValueHandler
pelo handleReturnValue
método.
Mas há outro problema aqui. Quando verificarmos o console, encontraremos a seguinte exceção:
javax.servlet.ServletException: Caminho de visualização circular [test1]: seria despachado de volta para o URL do manipulador atual [/ test1] novamente. Verifique a configuração do ViewResolver! (Dica: isso pode ser o resultado de uma visão não especificada, devido à geração do nome da visão padrão.) Em org.springframework.web.servlet.view.InternalResourceView.prepareForRendering (InternalResourceView.java:209) ~ [spring-webmvc-5.1. 2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel (InternalResourceView.java:147) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2 .RELEASE] em org.springframework.web.servlet.view.AbstractView.render (AbstractView.java:316) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.DispatcherServlet.render (DispatcherServlet.java:1370) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.DispatcherServlet.processDispatchResult (DispatcherServlet.java:1116) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.DispatcherServlet. doDispatch (DispatcherServlet.java:1055) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.DispatcherServlet.doService (DispatcherServlet.java:942) ~ [spring -webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.FrameworkServlet.processRequest (FrameworkServlet.java:998) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.servlet.FrameworkServlet.doGet (FrameworkServlet.java:890) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em javax.servlet.http.HttpServlet.service (HttpServlet.java:634) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.springframework.web.servlet.FrameworkServlet.service (FrameworkServlet.java:875) ~ [spring-webmvc-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em javax.servlet.http.HttpServlet.service ( HttpServlet.java:741) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.internalDoFilter (ApplicationFilterChain.java:231) ~ [tomcat-embed-core -9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.doFilter (ApplicationFilterChain.java:166) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.tomcat.websocket.server.WsFilter. doFilter (WsFilter.java:53) ~ [tomcat-embed-websocket-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.internalDoFilter (ApplicationFilterChain.java:193) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.doFilter ( ApplicationFilterChain.java:166) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.springframework.web.filter.RequestContextFilter.doFilterInternal (RequestContextFilter.java:99) ~ [spring-web-5.1 .2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.filter.OncePerRequestFilter.doFilter (OncePerRequestFilter.java:107) ~ [spring-web-5.1.2.RELEASE.jar: 5.1.2. RELEASE] em org.apache.catalina.core.ApplicationFilterChain.internalDoFilter (ApplicationFilterChain.java:193) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.doFilter (ApplicationFilterChain.java:166) ~ [tomcat-embed-core-9.0.12.jar: 9.0. 12] em org.springframework.web.filter.FormContentFilter.doFilterInternal (FormContentFilter.java:92) ~ [spring-web-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.filter.OncePerRequestFilter.doFilter (OncePerRequestFilter.java:107) ~ [spring-web-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.apache.catalina.core.ApplicationFilterChain. internalDoFilter (ApplicationFilterChain.java:193) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.doFilter (ApplicationFilterChain.java:166) ~ [tomcat-embed -core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.internalDoFilter (ApplicationFilterChain.java:193) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal (HiddenHttpMethodFilter.java:93) ~ [spring-web-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.filter.OncePerRequestFilter.doFilter (OncePerRequestFilter.java:107) ~ [spring-web-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.apache.catalina.core.ApplicationFilterChain.internalDoFilter (ApplicationFilterChain.java:193) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.ApplicationFilterChain.doFilter (ApplicationFilterChain.java:166) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal ( CharacterEncodingFilter.java:200) ~ [spring-web-5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.springframework.web.filter.OncePerRequestFilter.doFilter (OncePerRequestFilter.java:107) ~ [spring-web -5.1.2.RELEASE.jar: 5.1.2.RELEASE] em org.apache.catalina.core.ApplicationFilterChain.doFilter (ApplicationFilterChain.java:166) ~ [tomcat-embed-core-9.0.12.jar: 9.0. 12] em org.apache.catalina.core.StandardWrapperValve.invoke (StandardWrapperValve.java:199) ~ [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.StandardContextValve.invoke ( StandardContextValve.java:96) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.authenticator.AuthenticatorBase.invoke (AuthenticatorBase.java:490) [tomcat-embed-core-9.0 .12.jar: 9.0.12] em org.apache.catalina.core.StandardHostValve.invoke (StandardHostValve.java:139) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache. catalina.valves.ErrorReportValve.invoke (ErrorReportValve.java:92) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina.core.StandardEngineValve.invoke (StandardEngineValve.java:74) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.catalina .connector.CoyoteAdapter.service (CoyoteAdapter.java:343) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.coyote.http11.Http11Processor.service (Http11Processor.java:408) [ tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.coyote.AbstractProcessorLight.process (AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.coyote.AbstractProtocol $ ConnectionHandler.process (AbstractProtocol.java : 770) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.tomcat.util.net.NioEndpoint $ SocketProcessor.doRun (NioEndpoint.java:1415) [tomcat-embed-core- 9.0.12.jar: 9.0.12] em org.apache.tomcat.util.net.SocketProcessorBase.run (SocketProcessorBase.java:49) [tomcat-embed-core-9.0.12.jar: 9.0.12] em org.apache.tomcat.util.threads.TaskThread $ WrappingRunnable.run (TaskThread.java:61) [tomcat- embed-core-9.0.12.jar: 9.0.12] em java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1149 ) [na: 1.8.0_171] em java.util.concurrent.ThreadPoolExecutor $ Worker.run (ThreadPoolExecutor.java:624) [na: 1.8.0_171] em java.lang.Thread.run (Thread.java:748) [na: 1.8.0_171]
Isso ocorre porque, no Spring, se o método no Controlador não estiver @ResponseBody
marcado, o valor de retorno será considerado como o nome da visão por padrão, e aqui não queremos que o valor das Propriedades resolvido seja considerado o nome do view, então precisamos estar na última linha PropertiesHandlerMethodReturnValueHandler
do handleReturnValue
método Adicione o seguinte código:
// 告诉 Spring MVC 请求已经处理完毕
mavContainer.setRequestHandled(true);
Esta linha de código diz ao Spring que a solicitação foi concluída com sucesso e nenhum processamento adicional é necessário. Reinicie o projeto e visite a solicitação acima novamente, e o console não lançará mais exceções.