消息转换器原理解析

消息转换器原理解析

在使用Spring框架过程中,很多框架内部实现都涉及到消息转换器。

  • Spring MVC框架中,将HTTP请求信息转换为一个对象(@RequestBody注解),将对象输出为HTTP响应信息(@ResponseBody注解),都通过消息转换器HttpMessageConverter来进行不同类型对象转换。

  • 在操作Redis数据库时,一般选用RedisTemplateStringRedisTemplate,如何将对象存储到redis中,就涉及到序列化方式的选择,不同序列化方式,结果不一样,虽然序列化器不是转换器,但作用大体是一样的。

  • 在使用RabbitMQ消息队列中,生产者需要将对象转换成消息写进消息队列,消费者需要将消息转换成对象读取,都离不开消息转换器MessageConverter进行消息转换。

    我们知道存储到数据到本地磁盘或者传输数据到网络另一端,都是以字节为最小单位进行的,所以在存储或传输对象时,最终是对象与具体字节数据相互转换,而类型转换器或序列化框架作用正是如此。

HTTP类型转换器

1、转换原理图
在这里插入图片描述
从上图知,发送消息时,消息转换器器作用是将对象转换为某一格式报文,然后将报文发送另一端(@ResponseBody注解);接收消息时,消息转换器作用是将某一格式报文转换为对象(@RequestBody注解)。

2、Spring MVC框架内置了很多HTTP消息转换器,不同消息类型转换器处理不同Content-type类型数据。如MappingJackson2HttpMessageConverter处理请求类型为application/json类型数据,StringHttpMessageConverter处理类型为为text/html类型数据等。在框架内部会根据不同请求类型值选择不同类型转换器进行消息转换。

3、RestTemplate VS HttpClient
RestTemplateHttpClient都是处理HTTP客户端工具,其中RestTemplate内部内置消息转换器,在一定程度上减少代码开发,下面以一个例子来说明:
一、定义一个简单的restful接口

@RestController
![2018-11-30_10827](2018-11-30_10827.png)

public class UserController
{
    @GetMapping(value = "/getUser")
    public User getUserInfo(){
        User user =new User();
        user.setUserName("test");
        return user ;
    }
}

二、使用RestTemplate访问该服务

String url = "http://localhost:8080/getUser"; //请求地址
RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForObject(url, User.class);

从这个例子可以看出,服务端通过@ResponseBody注解,默认返回数据类型ContentType值为application/json,返回数据为json格式数据,客户端RestTemplate通过ContentType选择内置转换器为MappingJackson2HttpMessageConverterjson格式数据转换为具体User对象。

三、下图为RestTemplate构造方法默认实现:

 public RestTemplate() {
        this.messageConverters = new ArrayList();
        this.errorHandler = new DefaultResponseErrorHandler();
        this.uriTemplateHandler = new DefaultUriTemplateHandler();
        . . .
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        if (jackson2Present) {
            this.messageConverters.add(new MappingJackson2HttpMessageConverter());
        } else if (gsonPresent) {
            this.messageConverters.add(new GsonHttpMessageConverter());
        }

    }

四、如果使用HttpClient,还需要手动将json数据转换为具体对象,调用以下代码:

 User user =JsonUtil.fromJson(json, User.Class); 

使用RestTemplate,由于内置消息转换器,通过转换器自动完成json格式数据与对象转换,不需要在写代码进行json格式字符串与对象转换。

Redis对象操作存储

1、RedisTemplateStringRedisTemplate 默认序列化方式

  • RedisTemplate默认采用JDK序列化方式对对象进行存储,在控制台上看到是一堆乱码,由于控制台采用new String(byte [])进行解码,两种序列化方式不一致,导致显示乱码。而StringRedisTemplate采用string.getBytes(this.charset)对字符串进行序列化

2、更改对象存储方式, 采用json格式存储,默认提供了
Jackson2JsonRedisSerializerGenericJackson2JsonRedisSerializer
两种序列化方式。

  • 这两种序列化方式都能将对象转换成json格式存储在redis服务器中
  • Jackson2JsonRedisSerializer存储对象是不带类型,存储结构如下:
127.0.0.1:6379> get bbb
"{\"id\":11,\"name\":\"\xe5\xbc\xa0\xe4\xb8\x89\",\"no\":null}"

GenericJackson2JsonRedisSerializer存储对象是带有Type类型的,通过类型可以知道要转换的类型,具体存储结构如下图(存储类型为:com.redis.springredis.Strudent):

127.0.0.1:6379> get aaa 
"[\"com.redis.springredis.Strudent\",{\"id\":11,\"name\":\"\xe5\xbc\xa0\xe4\xb8\
x89\"}]"

3、如何选择哪一种序列化类进行json格式存储?
一般采用GenericJackson2JsonRedisSerializer进行序列化存储统一处理。
我们只需要创建一个RedisTemplate对象。如果选用Jackson2JsonRedisSerializer,则对每个类型创建不同的RedisTemplate对象。如存储User对象,则需要创建User类型的RedisTemplate(new RedisTemplate<String, User>),要存储Integer类型对象,则需要创建Integer类型的RedisTemplate(new RedisTemplate<String, Integer>),而GenericJackson2JsonRedisSerializer解决此问题,因为存储时候存储具体类型,就知道要转换什么类型。

RabbitMQ消息转换器

1、转换原理图
在这里插入图片描述
生产者传送消息对象时,生产端消息转换器将对象转换成某一格式(如json格式)字节数据,消费者接收到对应字节数据,根据类型、消费端的消息转换器将字节数据转换成具体对象类型。
2、使用RabbitTemplate进行消息发送,默认消息转换器是什么

 public RabbitTemplate() {
        ......
        this.messageConverter = new SimpleMessageConverter();
        this.messagePropertiesConverter = new DefaultMessagePropertiesConverter();
        this.initDefaultStrategies();
    }

从构造函数可以看出,默认转换器使用SimpleMessageConverter,如何更改发送端消息转换器?
只需要在发送端配置文件中定义一个bean。

@Bean public MessageConverter messageConverter(){
    return  new Jackson2JsonMessageConverter();
}

相关源代码:
查看RabbitAutoConfiguration类中静态内部类RabbitTemplateConfiguration中rabbitTemplate生成

@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean({RabbitTemplate.class})
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
    MessageConverter messageConverter = (MessageConverter)this.messageConverter.getIfUnique();
    if (messageConverter != null) {
        rabbitTemplate.setMessageConverter(messageConverter);
    }}
     ......
}

由于指定了消息转换器为Jackson2JsonMessageConverter,则messageConverter.getIfUnique()方法生成对象指向定义的Jackson2JsonMessageConverter
转换器。

使用json格式转换器,具体发送消息可以通过MQ控制台查看具体消息内容,如下:
在这里插入图片描述

3、AMQP Message 数据结构

public class Message implements Serializable {
    private static final long serialVersionUID = -7177590352110605597L;
    private static final String ENCODING = Charset.defaultCharset().name();
    private static final SerializerMessageConverter SERIALIZER_MESSAGE_CONVERTER = new SerializerMessageConverter();
    private final MessageProperties messageProperties;
    private final byte[] body;}

从该类可以看出,body内容为发送对象消息根据发送端消息转换器序列化后的二进制内容。MessageProperties 为一个properties文件,主要存储消息头部信息,如content_type:表明消息格式为json格式字符串,content_encoding:表明字符串的编码格式为UTF-8编码。TypeId:表明生产端发送对象是Student类型对象(具体作用后面介绍)。

2、接收端如何接收数据:
接收端代码如下,那么消费端会使用哪一种方式接收数据,通过运行知道会通过process1进行消息处理

@Component @RabbitListener(queues = "hello")
public class Receiver1 {

    public Receiver1(){
        System.out.println("改造函数执行");
    }

    @RabbitHandler
  public void process1(byte[] person) throws UnsupportedEncodingException {
        System.out.println("自带消息转换器 : " + new String(person,"UTF-8"));
    }

    @RabbitHandler
  public void process2(List<Person> person) {
        System.out.println("集合 Receiver : " + person);
    }

    @RabbitHandler
  public void process3(Student person) {
        System.out.println("单独对象Receiver : " + person);
    }
}

原理如下:
SimpleRabbitListenerContainerFactory默认转换器为SimpleMessageConverter,我们可以看看SimpleMessageConverterfromMessage方法,该方法作用是将消息转换为具体对象。
在这里插入图片描述
从上图知,由于发送端content_type为json格式,所以经过默认转换器转换后Object值类型为byte[].然后根据消费者提供方法,进行反射,找到消费端处理方法含有入参参数为byte[] 字节数组方法,正好process1方法满足条件。

如何更改接收端的消息转换器,方式和发送端类似,在配置类定义消息转换器,如果此时使用消息转换器为Jackson2JsonMessageConverter
在这里插入图片描述
从上图知,targetJavaType值为Student类型对象,需要消费端存在Student对象,如果消费端不存在,则报转换异常,如果存在,则转换为相应Student对象。使用json转换器,要求服务端定义与客户端相同的类。

总结:

1、通过上述分析,合理使用消息中间件,能很大程度上减少开发中重复代码。
2、掌握消息中间件转换原理,能较快解决工作中遇到各种问题。

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值