在kafka中,broker希望收到的消息的key和value都是字节数组,所以在创建生产者对象的时候必须指定序列化器。将消息进行序列化才可以进行网络传输,在kafka中默认提供了ByteArraySerializer(字节数组序列化器)、StringSerializer(字符串序列化器)、IntegerSerializer(整形序列化器)。如果发送到Kafka的消息不是提供的这几种类型,那么可以使用序列化框架来创建消息记录,如Avro、Thrift、Protobuf,或者使用自定义序列化器。但是如果使用自定义序列化器,可能在开发的过程中会有需求的变动,比如实体类增加一个字段,那么所有用到这个实体类的代码都需要修改,会出现新旧消息的兼容性问题因此不建议使用自定义的序列化器。但是在这里我们自己实现以下自定义的序列化器有助于我们理解序列化器的工作原理。
下面就来实现一下自定义序列化器。
1.创建项目,编写pom文件:
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.12</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
2.编写一个实体类作为我们要发送的数据类型:
package demo;
public class Student{
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
public Student(String name,int age){
super();
this.name=name;
this.age=age;
}
@Override
public String toString(){
return "Student [name=" + name + ",age=" + age+ "]";
}
}
3.定义序列化类:
package demo;
import java.nio.ByteBuffer;
import java.util.Map;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Serializer;
public class MySerializer implements Serializer<Student>{
@Override
public void configure(Map<String, ?> configs, boolean isKey){
//不做任何配置
}
@Override
public byte[] serialize(String topic, Student data){
try{
byte[] serializedName;//将name属性变成字节数组
int serializedLength;//表示name属性的值的长度
if(data == null){
return null;
}else{
if(data.getName() != null){
serializedName = data.getName();.getBytes("UTF-8");
serializedLength = serializedName.length;
}else{
serializedName = new byte[0];
serializedLength = 0;
}
}
//我们要定义一个buffer用来存储age的值,age是int类型,需要4个字节。还要存储name的值的长度(后面会分析为什么要存name的值的长度),用int表示就够了,也是4个字节,name的值是字符串,长度就有值来决定,所以我们要创建的buffer的大小就是这些字节数加在一起:4+4+name的值的长度
ByteBuffer buffer = ByteBuffer.allocate(4+4+serializedLength);
//put()执行完之后,buffer中的position会指向当前存入元素的下一个位置
buffer.putInt(data.getAge());
//由于在读取buffer中的name时需要定义一个字节数组来存储读取出来的数据,但是定义的这个数组的长度无法得到,所以只能在存name的时候也把name的长度存到buffer中
buffer.putInt(serializedLength);
buffer.put(serializedName);
return buffer.array();
}catch(Exception e){
throw new SerializationException("error when serializing..."+e);
}
}
@Override
public void close(){
//不需要关闭任何东西
}
}
4.自定义反序列化类:
package demo;
import java.nio.ByteBuffer;
import java.util.Map;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Deserializer;
public class MyDeserializer implements Deserializer<Student>{
@Override
public void configure(Map<String, ?> configs, boolean isKey){
}
@Override
public Student deserialize(String topic, byte[] data){
int age;
int nameLength;
String name;
try{
if(data == null){
return null;
}
if(data.length < 8){
throw new SerializationException("Size of data received by IntegerDeserializer is shorter than expected...");
}
ByteBuffer buffer = ByteBuffer.wrap(data);//wrap可以把字节数组包装成缓冲区ByteBuffer
//get()从buffer中取出数据,每次取完之后position指向当前取出的元素的下一位,可以理解为按顺序依次读取
age = buffer.getInt();
nameLength = buffer.getInt();
/*
* 定义一个字节数组来存储字节数组类型的name,因为在读取字节数组数据的时候需要定义一个与读取的数组长度一致的数组,要想知道每个name的值的长度,就需要把这个长度存到buffer中,这样在读取的时候可以得到数组的长度方便定义数组
*/
byte[] nameBytes = new byte[nameLength];
buffer.get(nameBytes);
name = new String(nameBytes, "UTF-8");
return new Student(name, age);
}catch(Exception e){
throw new SerializationException("error when deserializing..."+e);
}
}
@Override
public void close(){
}
}
5.创建生产者:
package demo;
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
public class MyProducer{
public static void main(String[] args){
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.184.128:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//设置value的序列化类为自定义序列化类全路径
props.put("value.serializer", "demo.MySerializer");
Producer<String, Student> producer = new KafkaProducer<String, Student>(props);
//定义一个要发送的Student类型的value
Student s = new Student("Bob", 14);
Student s2 = new Student("Sam", 17);
//参数一为topic,参数二为value
ProducerRecord<String, Student> record = new ProducerRecord<String, Student>("WYH-serializer-topic", s);
ProducerRecord<String, Student> record1 = new ProducerRecord<String, Student>("WYH-serializer-topic", s2);
producer.send(record);
producer.send(record1);
producer.close();
}
}
6.创建消费者:
package demo;
import java.util.Collections;
import java.util.Properties;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.ConsumerRecord;
public class MyConsumer{
public static void main(String[] args){
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.184.128:9092");
props.put("group.id", "WYH-deserializer-app");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//设置value的反序列化类为自定义序列化类全路径
props.put("value.deserializer", "demo.MyDeserializer");
props.put("auto.offset.reset", "earliest");
KafkaConsumer<String, Student> consumer = new KafkaConsumer<String, Student>(props);
consumer.subscribe(Collections.singletonList("WYH-serializer-topic"));
while(true){
CosumerRecords<String, Student> records = consumer.poll(100);
for(ConsumerRecord<String, Student> consumerRecord: records){
Student student = consumerRecord.value();
System.out.println(student.toString());
}
}
}
}
7.启动消费者,再启动生产者,查看控制台打印信息:
这样就实现了对实体类的数据类型的序列化。