Personalize a negociação de conteúdo do Spring Boot

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

HttpMessageConverterPara 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, HttpMessageConvertera classe de implementação MappingJackson2HttpMessageConverterusada será selecionada para processar o valor de retorno.

HttpMessageConverter personalizado

Além da HttpMessageConverterimplementação fornecida pelo Spring , também podemos personalizar HttpMessageConvertera 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 MappingJackson2HttpMessageConverterimplementá- lo com referência ao método de implementação e visualizar MappingJackson2HttpMessageConvertero diagrama do protótipo:

Portanto, podemos AbstractGenericHttpMessageConverterimplementar a HttpMessageConverterinterface por meio de herança .

Crie um novo projeto Spring Boot, a versão é 2.1.0.RELEASE, e introduza spring-boot-starter-webdependências, a estrutura do projeto é a seguinte:

Criamos com.example.demoum novo converterpacote no caminho , em seguida, criamos PropertiesHttpMessageConvertere 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 readxxxestá 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 readInternalmétodo, obtemos o fluxo de entrada e a codificação no corpo da solicitação HTTP e, em seguida, chamamos o loadmétodo do objeto Propriedades para converter o fluxo em um objeto Propriedades. Após a conclusão do processo de desserialização, ainda precisamos PropertiesHttpMessageConverteradicioná-lo à HttpMessageConvertercoleção.

com.example.demoCrie um novo configpacote no caminho e, em seguida, crie uma WebConfigurerclasse de configuração:

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

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

extendMessageConvertersO método é WebMvcConfigurero método padrão, aqui nós reescrevemos esse método para PropertiesHttpMessageConverteradicioná-lo à coleção do conversor de mensagem.

Em seguida, crie um controlador para testar uma onda, crie um com.example.demonovo controllerpacote 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 @GetMappinganotado . Se o método pode ser chamado com sucesso e pode retornar um objeto, significa que nosso conversor de mensagem HTTP personalizado é viável.consumestext/propertiesProperties

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 PropertiesHttpMessageConverterprecisamos especificar o tipo de mídia que ele pode manipular em seu construtor quando o personalizamos , vamos verificar MappingJackson2HttpMessageConvertero construtor para ver como ele é implementado:

Portanto, PropertiesHttpMessageConverteradicionamos 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 PropertiesHttpMessageConverterdo writeInternalmé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:

  1. Aqui definimos a interface REST, portanto, a resposta será serializada no formato JSON por padrão;

  2. 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 JSON MappingJackson2HttpMessageConverter.

Podemos usar a depuração para verificar PropertiesHttpMessageConverterse 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 PropertiesHttpMessageConverterestá 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 writeInternalmé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 Propertiesdo storemé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 @RequestBodye @ResponseBodyanotações. Além disso, também podemos lidar com negociação de conteúdo , personalizando HandlerMethodArgumentResolvere HandlerMethodReturnValueHandleraulas de execução.

HandlerMethodArgumentResolverComumente conhecido como analisador de parâmetros do método, é usado para analisar @RequestMappingos parâmetros do método marcado pela anotação (ou suas anotações derivadas). Aqui, começamos HandlerMethodArgumentResolvera analisar automaticamente o conteúdo do corpo da solicitação HTTP em um objeto Propriedades por meio de implementação .

com.example.demoCrie um novo resolverpacote no caminho e, em seguida, crie PropertiesHandlerMethodArgumentResolver

Implemente a 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;
    }
}

O método é supportsParameterusado para especificar os tipos de parâmetro que oferecem suporte à análise, aqui está o tipo de Propriedades. resolveArgumentO método é usado para implementar a lógica de análise, e o processo de análise é semelhante ao PropertiesHttpMessageConverterdo readInternalmétodo definido acima .

Em seguida, precisamos PropertiesHandlerMethodArgumentResolveradicioná-lo à HandlerMethodArgumentResolvercoleção de classes de implementação que vem com o Spring . É importante ressaltar que não podemos adicionar WebMvcConfigurerreescrevendo 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á- RequestMappingHandlerAdapterlo 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 WebConfigurerclasse de configuração é montada, redefinimos a coleção do analisador de método por meio requestMappingHandlerAdapterdo setArgumentResolversmétodo do objeto e será PropertiesHandlerMethodArgumentResolveradicionado à primeira posição da coleção.

A razão para PropertiesHandlerMethodArgumentResolveradicioná-lo à primeira posição é porque Properties é essencialmente um objeto Map, e Spring é integrado MapMethodProcessorpara lidar com os tipos de parâmetro Map. Se a PropertiesHandlerMethodArgumentResolverprioridade não for aumentada, então os parâmetros do tipo Properties serão MapMethodProcessoranalisados ​​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;
    }
}

test1Os parâmetros do método não estão @RequestBodymarcados, 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 PropertiesHttpMessageConverterdo writeInternalmétodo ainda é analisado pelo método e depende da @ResponseBodyanotação, e então começamos a implementar um método personalizado analisador valor de retorno, e não dependem da @ResponseBodyanotação.

Custom HandlerMethodReturnValueHandler

HandlerMethodArgumentResolverNormalmente conhecido como analisador de valor de retorno do método, ele é usado para analisar @RequestMappingo valor de retorno do método marcado pela anotação (ou suas anotações derivadas). Aqui, começamos a HandlerMethodReturnValueHandlerpersonalizar um analisador para processar o tipo de valor de retorno do tipo Propriedades por meio de implementação .

com.example.demoCrie um novo handlerpacote no caminho e, em seguida, crie uma PropertiesHandlerMethodReturnValueHandlerimplementaçã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");
    }
}

supportsReturnTypeO método de processamento é designado tipo de valor de retorno, handleReturnValueum método para processar um valor de retorno, onde a lógica e PropertiesHttpMessageConvertero writeInternalmétodo são basicamente os mesmos, ele é omitido.

Em seguida, ele será PropertiesHandlerMethodReturnValueHandleradicionado à HandlerMethodReturnValueHandlercoleção de classes de implementação que vem com o Spring , e o método de adição é o HandlerMethodArgumentResolvermesmo 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 TestControllerdo test1método @ResponseBody, reiniciamos o projeto e visitamos novamente:

Como você pode ver, o valor de retorno foi sucesso analisado PropertiesHandlerMethodReturnValueHandlerpelo handleReturnValuemé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 @ResponseBodymarcado, 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 PropertiesHandlerMethodReturnValueHandlerdo handleReturnValuemé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.

 

 

Acho que você gosta

Origin blog.csdn.net/u014225733/article/details/100858053
Recomendado
Clasificación