Análise dos princípios do conversor de mensagens MessageConverter e negociação de conteúdo no SpringBoot

     

Índice

1. Princípio do processador de valor de retorno ReturnValueHandler

2. Use o conversor de mensagens MessageConverter para processar e gravar os dados como json

3. Princípio do HttpMessageConverter

4. Princípio da negociação de conteúdo

5. Personalize o MessageConverter

6. Gerenciador de negociação de conteúdo customizado (estratégia de negociação de conteúdo customizada)


        Como todos sabemos, o framework SpringBoot oferece comodidade para nosso trabalho de desenvolvimento, reduzindo bastante a configuração complicada ao desenvolver usando o framework SSM (Spring, SpringMVC, MyBatis).

        Isso ocorre porque a estrutura SpringBoot executa automaticamente muitas classes de configuração automática AutoConfiguration para nós no nível inferior, incluindo a classe de configuração automática  WebMvcAutoConfiguration para cenários da Web  . Essa classe de configuração automática registra automaticamente para nós muitos componentes que precisamos usar ao desenvolver cenários da Web. o contêiner do COI .

       Entre muitos componentes, o conversor de mensagens MessageConverter nos permite converter tipos de dados Java e tipos de dados de mídia, como json/xml, entre si. Em seguida, marcamos  a anotação @ResponseBody do método Controller -> obtemos o processador de valor de retorno ReturnValueHandler - —> Obtenha o conversor de mensagens MessageConverter para negociação de conteúdo —> Análise detalhada de como o conversor de mensagens MessageConverter responde com dados...

public class WebMvcAutoConfiguration {

......


 @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
        
        ......
        
         public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            this.messageConvertersProvider.ifAvailable((customConverters) -> {
                converters.addAll(customConverters.getConverters());
            });
        }

        ......
    }

......

}

1. Princípio do processador de valor de retorno ReturnValueHandler

1. Primeiro crie uma classe Controller comum, retorne dados Person personalizados e marque a  anotação @ResponseBody no método.

@Controller
public class PersonController {

    @ResponseBody
    @GetMapping("/test/person")
    public Person test(){
        Person person = new Person();
        person.setName("xiaoming");
        person.setAge(20);
        return person;
    }
}

2. Inicie o programa no modo de depuração e chegue ao código onde realmente executamos o método de destino.

3. Entrando no método, a camada inferior do SpringMVC primeiro percorre todos os analisadores de valor de retorno returnValueHandlers 

 Coloque-o em  um método executável invocableMethod

 4. Em seguida, a camada inferior do SpringMVC usará o mecanismo de reflexão para executar o método de destino. Quando o método de destino for executado, obteremos nosso próprio valor de retorno, que é nosso objeto Person. Depois de obter o valor de retorno, usaremos o analisador de valor de retorno salvo anteriormente. para nos ajudar a lidar com o valor de retorno

 5. A primeira etapa no processamento do valor de retorno é percorrer todos os processadores de valor de retorno para ver qual processador de valor de retorno pode lidar com nosso valor de retorno atual.Finalmente, obtemos o RequestResponseBodyMethodProcessor que pode lidar com nosso método atual anotado com a anotação  @ResponseBody . .

 

        O manipulador de valor de retorno é na verdade uma interface que define dois métodos.

        (1)  supportReturnType : determine se o processador de valor de retorno atual suporta este tipo de valor de retorno

        (2)  handleReturnValue : Se suportado, este método será chamado para realmente processá-lo.

  

 6. Depois de obter qual processador de valor de retorno pode lidar com o valor de retorno atual,  o   método handleReturnValue do processador de valor de retorno será executado.

         Após inserir o método, uma série de dados como solicitação e resposta serão obtidos por meio de uma série de configurações.Finalmente, writeWithMessageConverters() é chamado para passar nosso conteúdo de valor de retorno, tipo de valor de retorno, solicitação e resposta para usar o conversor de mensagem para realizar a operação de gravação.

2. Use o conversor de mensagens MessageConverter para processar e gravar os dados como json

        Aceitar (Tradução: Aceitar), ou seja, quando o navegador ou cliente envia uma solicitação, ele informará ao servidor na forma de cabeçalhos de solicitação que tipo de parâmetros ele pode receber. Ele aceitará páginas HTML primeiro, e mesmo que seja não for possível, também pode receber todos os tipos, como imagens.

A página HTML tem o maior peso, 0,9

*/* Ou seja, todos os tipos de dados são aceitáveis ​​e o peso é 0,8

 1. No código-fonte de negociação de conteúdo subjacente do SpringBoot, primeiro determine se esta solicitação foi processada antecipadamente (ou seja, o tipo de mídia não foi determinado; em caso afirmativo, use o tipo de mídia determinado anteriormente) e, em seguida, insira o else branch para obter esta solicitação primeiro. Solicite a solicitação nativa e, em seguida, chame o método getAcceptableMediaTypes() para obter os tipos de mídia que o navegador pode receber.

Em seguida, o servidor chama o método getProducibleMediaTypes() para obter os tipos de dados aos quais o servidor pode responder.

        O interior do método getProducibleMediaTypes() na verdade percorre cada conversor de mensagem na parte inferior do SpringMVC para determinar qual conversor de mensagem pode processar os dados de resposta e armazená-los no resultado.

2. Depois de obter os tipos que o navegador ou cliente deseja receber e todos os tipos de dados aos quais o servidor pode responder, por meio de loop, os tipos que o navegador pode receber e os tipos aos quais o servidor pode responder começam a corresponder, e eventualmente será armazenado em mediaTypesToUse .Que

 3. Depois de decidirmos que tipo de dados usar, a camada inferior do SpringMVC percorrerá todos os conversores de mensagens para ver quem pode lidar com esse tipo de dados (assumindo aqui que o tipo de dados a ser respondido ao navegador seja json).

4. O loop subjacente do SpringMVC determina se cada conversor pode processar o tipo json. Quando o loop passa para MappingJackson2HttpMessageConverter , ele entra no método canWrite() deste conversor de mensagem . O resultado final é verdadeiro, ou seja, MappingJackson2HttpMessageConverter pode processar dados do tipo json .

 5. Após obter o conversor de mensagens que desta vez pode processar os dados de resposta, o código-fonte vai para o método de gravação real.

 Obtenha o cabeçalho de resposta desta resposta no método write(). O "Content-Type" no cabeçalho de resposta é do tipo json.

6. Finalmente, após uma série de processamento no método write(), o tipo de dados Person é convertido para o tipo json e os dados são gravados em outputMessage no formato json.

3. Princípio do HttpMessageConverter

        Cada conversor de mensagens na parte inferior do SpringMVC realmente implementa uma interface HttpMessageConverter.Eles possuem os seguintes métodos e seus próprios recursos de processamento correspondentes.

HttpMessageConverter: Veja se ele suporta a conversão deste objeto do tipo Class em dados do tipo MediaType

Descrição do método:

canRead() : Se o navegador enviar uma solicitação, este conversor de mensagens pode converter dados de tipos de dados de mídia, como JSON, para o tipo Pessoa?

canWrite() : Você pode escrever o objeto Person retornado pelo servidor como um tipo de dados de mídia, como dados json, e responder ao navegador?

getSupportedMediaTypes() : a camada inferior do SpringMVC usa o método getSupportedMediaTypes() para contar qual conteúdo todos os MessageConverters podem gravar.

read() : operação de leitura específica

write() : operação de gravação específica

Se for uma resposta, deverá ser escrita; se for uma solicitação, deverá ser transferida de volta.

4. Princípio da negociação de conteúdo

        Quando o método no Controller é marcado com a anotação @ResponseBody, porque no cenário de desenvolvimento SpringBoot, o cenário Web importa automaticamente a dependência json para nós, então o servidor responde ao navegador com dados do tipo json por padrão, mas se você quiser para retornar dados do tipo xml, basta introduzir as dependências relevantes no arquivo POM

<!--引入XML依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

1. Primeiro determine se existe um determinado tipo de mídia no cabeçalho de resposta atual, porque o interceptador pode tê-lo processado antecipadamente e codificado o tipo de dados. Se houver, use o tipo de dados determinado anteriormente.

2. Em seguida, determine que tipo de dados de resposta o cliente ou navegador pode receber.

        (1) No método getAcceptableMediaTypes() , existe um contentNegotiationManger (gerenciador de negociação de conteúdo) , que usa a estratégia de negociação de conteúdo baseada no cabeçalho da solicitação por padrão.

         Se  HeaderContentNegotiationStrategy for  uma estratégia de negociação de conteúdo baseada no cabeçalho da solicitação , ela usará a solicitação de solicitação nativa para obter o valor do campo Aceitar do cabeçalho da solicitação para determinar o tipo de mídia que o cliente (Postman) ou navegador pode receber.

         (2) No entanto, às vezes os navegadores não podem alterar arbitrariamente o campo Aceitar do cabeçalho da solicitação (a menos que enviem uma solicitação Ajax).Para facilitar a negociação do conteúdo, podemos adicionar a seguinte configuração ao arquivo de configuração global (application.properties/yml ) Neste momento, desde que Quando o navegador enviar uma solicitação com o parâmetro format=json/xml , a função de negociação de conteúdo baseada em parâmetros estará habilitada.

spring.mvc.contentnegotiation.favor-parameter=true

Se a função de negociação de conteúdo baseada em parâmetros          estiver ativada , haverá uma estratégia de negociação de conteúdo baseada em parâmetros no contentNegotiation (gerenciador de negociação de conteúdo).

A camada inferior da estratégia de negociação de conteúdo baseada          obterá para nós o valor do formato no cabeçalho da solicitação . Após obter esse valor, é determinado o tipo de dados de mídia a ser recebido pelo navegador.

​ Então podemos enviar a seguinte solicitação com parâmetros de formato no navegador

http://localhost:8080/test/person?format=json

http://localhost:8080/test/person?format=xml 

 3. Depois de obter quais tipos de dados o cliente ou navegador pode receber, insira o método getProducibleMediaTypes() para determinar quais tipos de dados o servidor pode produzir em resposta (internamente, o método percorre todos os MessageConverters na parte inferior do Spring MVC para ver quem suporta a operação deste tipo de dados e os resultados estatísticos são armazenados no resultado)

​ 

 4.  Depois de determinar o tipo de dados que o navegador deseja o tipo de dados ao qual o servidor pode responder  , execute a negociação de conteúdo para obter o tipo de mídia mais adequado e armazene-o em mediaTypesToUse .

 5. Finalmente, após uma série de triagens, o tipo de dados selectMediaType desta resposta é finalmente determinado e, em seguida, ele percorre todos os MessageConverters novamente para ver quem pode suportar esse processamento (converter o objeto java para o tipo de mídia mais adequado json / xml , este loop MessageConverter subjacente do SpringMVC foi percorrido duas vezes, uma vez na etapa 3 para determinar quais dados todos os MessageConverters do servidor atual podem suportar juntos e novamente agora para ver quem pode suportar o processamento desse tipo de mídia mais correspondente )

 6. Depois de encontrar o MessageConverter que pode lidar com o melhor tipo de mídia correspondente , chame seu método write para converter.

5. Personalize o MessageConverter

        Se quisermos customizar o MessageConverter, só precisamos configurá-lo na classe de configuração WebMvcConfig customizada

1. Duas maneiras de personalizar WebMvcConfig

         (1) Personalize uma classe de configuração, crie um componente webMvcConfigurer na classe de configuração e coloque-o no contêiner IOC

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    //WebMvcConfigurer定制化SpringMvc的功能

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){

        *****实现WebMvcConfigurer中的方法实现扩展功能*****

        }
    }
}

        (2) Crie uma classe de configuração para implementar diretamente WebMvcConfigurer e implemente os métodos em WebMvcConfigurer na classe de configuração para implementar funções estendidas

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    *****实现WebMvcConfigurer中的方法实现扩展功能*****


}

(3) Não use a anotação @EnableWebMvc         ao personalizar WebMvcConfig . Usamos + aqui  para implementar regras personalizadas, porque usar@Configuration WebMvcConfigurer @EnableWebMvc + @Configuration + DelegatingWebMvcConfiguration 将全面接管SpringMVC,SpringMVC底层为我们调整好的所有默认配置都会失效。

2. Configure o  MessageConverter personalizado na classe de configuração WebMvcConfig personalizada

        (1) Escreva um  MessageConverter personalizado

public class PengpengMessageConverter implements HttpMessageConverter<Person> {
    /**
     * 支不支持把Person类型的数据读成某种媒体数据类型
     * 也就是浏览器传来json或xml类型的数据,Controller中的方法的入参位置标注@RequestBody注解时自动注入时转为Person类型
     * @param aClass
     * @param mediaType
     * @return
     */
    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
        return false;
    }

    /**
     * 只要响应的是Person类型就能自动转换成json或xml类型
     * @param aClass
     * @param mediaType
     * @return
     */
    @Override
    public boolean canWrite(Class<?> aClass, MediaType mediaType) {
        return aClass.isAssignableFrom(Person.class);
    }

    /**
     * 服务器要通过getSupportedMediaTypes()来统计所有的MessageConverter都能写出哪些内容
     * @return  这里要告诉SpringMvc我们要操作"application/x-pengpeng"自定义类型
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {  //获取所有支持的媒体类型
        return MediaType.parseMediaTypes("application/x-pp");
    }

    @Override
    public Person read(Class<? extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }
//
    @Override
    public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义协议数据的写出
        String data = person.getName() + ";" + person.getAge();
        //获取一个输出流
        OutputStream body = httpOutputMessage.getBody();
        //将自定义数据放到输出流中写出去
        body.write(data.getBytes());
    }
}

        (2) Escreva um WebMvcConfig personalizado

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    //WebMvcConfigurer定制化SpringMvc的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){

            //扩展自定义的MessageConverter
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new PengpengMessageConverter());
            }

        };

    }
}

        Neste momento, nosso  MessageConverter personalizado existirá na parte inferior do SpringMVC.Quando o valor do campo Aceitar no cabeçalho da solicitação enviada pelo cliente for "application/x-pp" e o SpringMVC realizar a negociação de conteúdo na parte inferior, ele obterá isso Quando o tipo de dados que o cliente deseja receber for "application/x-pp", a melhor correspondência será realizada por meio de negociação de conteúdo para obter nosso conversor de mensagens personalizado que pode lidar com esse tipo de dados e, em seguida, nosso conversor personalizado a mensagem será chamada O método de gravação do conversor responde com os dados.

6. Gerenciador de negociação de conteúdo customizado (estratégia de negociação de conteúdo customizada)

O MessageConverter         personalizado  acima só pode suportar a estratégia de negociação de conteúdo personalizada com base no cabeçalho da solicitação por padrão . Neste momento, se quisermos que o servidor suporte nossa estratégia de negociação de conteúdo personalizada com base em parâmetros , precisamos configurá-lo na classe de configuração personalizada.

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    //WebMvcConfigurer定制化SpringMvc的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){

            /**
             * 自定义内容协商管理器(自定义内容协商策略)
             * @param configurer
             */
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                Map<String, MediaType> map = new HashMap<>();
                map.put("xml",MediaType.APPLICATION_XML);
                map.put("json",MediaType.APPLICATION_JSON);
                //如果参数中是“pp”,对应自定义媒体类型
                map.put("pp",MediaType.parseMediaType("application/x-pp"));
                //指定支持解析哪些参数对应的哪些媒体类型,这里的有参构造放的是一个map集合
                ParameterContentNegotiationStrategy paramStrategy = new ParameterContentNegotiationStrategy(map);
                //基于请求头的处理策略
                HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
                //设置内容协商策略
                configurer.strategies(Arrays.asList(paramStrategy,headerStrategy));
            }

            //添加自定义的MessageConverter
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new PengpengMessageConverter());
            }

        };
    }
}

        Neste momento, nosso gerenciador de negociação de conteúdo personalizado substituirá o gerenciador de negociação de conteúdo padrão do SpringMVC subjacente. Desde que enviemos a solicitação http://localhost:8080/test/person?format=pp no ​​navegador, o SpringMVC subjacente saberá que o navegador deseja receber dados do tipo de mídia "application/x-pp" e, em seguida, usa a melhor correspondência com base na negociação de conteúdo para obter nosso conversor de mensagens personalizado que pode lidar com esse tipo de dados e, em seguida, chama nossa mensagem personalizada O método de gravação do conversor responde com os dados.

Se não adicionarmos uma política de negociação de conteúdo com base no cabeçalho da solicitação         ao personalizar (substituir o padrão) o gerenciador de negociação de conteúdo e se enviarmos uma solicitação normal sem negociação de conteúdo baseada em parâmetros , independentemente da solicitação que enviarmos ao cliente, o O valor do campo Accept no cabeçalho da solicitação é qualquer valor. Se não houver uma estratégia de negociação de conteúdo baseada no cabeçalho da solicitação na parte inferior do SpringMVC , o padrão será o tipo de dados de mídia que o navegador deseja receber nesta solicitação como * /* (ou seja, qualquer tipo), então A melhor correspondência de negociação de conteúdo corresponderá ao tipo de dados correspondente ao conversor de mensagens com o maior peso de acordo com o peso.

 

        O texto acima é minha análise dos princípios subjacentes do conversor de mensagens MessageConverter. Obrigado por assistir. Se você tiver opiniões diferentes, fique à vontade para estudar e discutir comigo ~~~

Acho que você gosta

Origin blog.csdn.net/weixin_64709241/article/details/129250427
Recomendado
Clasificación