文章目录
概要
想要成为一个写好java代码的Coder,泛型一定是不可少的。大家都听说过java的泛型是伪泛型,是编译期进行校验使用的,而不是在运行期生效。
怎么理解?
就是泛型里的类信息在实际运行的时候,java对象本身是获取不到的,需要从java对象的类信息才能获取到。
注:本文是通过protostuff框架,实现了protobuf的序列化/反序列化功能
protostuff框架地址
一个小demo
序列化
我们今天通过一个序列化/反序列化的例子来验证说明一下。我们的demo是基于kafka的序列化,反序列化接口来进行实现。只保留需要我们实现的接口方法
java
public interface Serializer<T> extends Closeable {
/**
* Convert {@code data} into a byte array.
*
* @param topic topic associated with data
* @param data typed data
* @return serialized bytes
*/
byte[] serialize(String topic, T data);
}
此时我们有个PO对象类:Company
java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Company {
private String name;
private String address;
}
然后我们很快写出来了
Company的序列化类
java
public class ProtoCompanySerializer implements Serializer<Company> {
@Override
public byte[] serialize(String topic, Company data) {
if (data == null) {
return null;
}
Schema<Company> schema = RuntimeSchema.getSchema(Company.class);
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
return ProtostuffIOUtil.toByteArray(data, schema, buffer);
}
}
看着很简单,是吧?不过如果我们有多个PO类呢?譬如还有一个User类,再写一个
java
public class ProtoUserSerializer implements Serializer<User> {
@Override
public byte[] serialize(String topic, User data) {
if (data == null) {
return null;
}
Schema<User> schema = RuntimeSchema.getSchema(User.class);
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
return ProtostuffIOUtil.toByteArray(data, schema, buffer);
}
}
这么写下去,确实是很清晰,但是我们很快就能发现这两个类的实现,除了入参类型不同、Schema泛型不同,其他都是一样的。那写个通用的不就行了?
java
public class ProtostuffSerializer implements Serializer {
@Override
public byte[] serialize(String topic, Object data) {
if (data == null) {
return null;
}
Schema schema = RuntimeSchema.getSchema(data.getClass());
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
return ProtostuffIOUtil.toByteArray(data, schema, buffer);
}
}
Object类型的对象作为入参,看着很别扭,是吧?但是实际运行没有任何问题,当然我们可以通过泛型,让代码看起来稍微优雅一些
java
public class ProtoCommonSerializer<T> implements Serializer<T> {
@Override
public byte[] serialize(String topic, T data) {
if (data == null) {
return null;
}
Schema<T> schema = (Schema<T>) RuntimeSchema.getSchema(data.getClass());
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
return ProtostuffIOUtil.toByteArray(data, schema, buffer);
}
}
但是这样的优雅其实并没有什么大用,因为为了支持一个序列化工具对象序列化多种类型的对象,实际我们在实例化ProtoCommonSerializer时,根本没法指定泛型

反序列化
java
public interface Deserializer<T> extends Closeable {
/**
* Deserialize a record value from a byte array into a value or object.
* @param topic topic associated with the data
* @param data serialized bytes; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
* @return deserialized typed data; may be null
*/
T deserialize(String topic, byte[] data);
有了上面的先验认知,我们当然想直接写通用的反序列化工具类,然后我们开始动笔,然后很快就发现一个问题,入参没有对象,我们没法判断入参的data这个字节数组要转化为哪个Class的对象
那换成上面后来写的泛型那种呢?
java
public class ProtoCommonDeserializer<T> implements Deserializer<T> {
@SneakyThrows
@Override
public T deserialize(String topic, byte[] data) {
if (data == null) {
return null;
}
Class<T> tClazz = (Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(), ProtoCommonDeserializer.class);
Schema<T> schema = RuntimeSchema.getSchema(tClazz);
T t = tClazz.newInstance();
ProtostuffIOUtil.mergeFrom(data, t, schema);
return t;
}
}
然后美滋滋写个单测
java
@Test
public void testDeserialize1() {
String topic = "abc";
Company company = new Company("wangsansan", "not same place");
ProtostuffSerializer protostuffSerializer = new ProtostuffSerializer();
byte[] data = protostuffSerializer.serialize(topic, company);
ProtoCommonDeserializer<Company> protoCommonDeserializer = new ProtoCommonDeserializer<Company>();
Company company2 = protoCommonDeserializer.deserialize(topic, data);
assert "wangsansan".equals(company2.getName());
}
一跑,哗擦,空指针。看了下异常栈,Schema<T> schema = RuntimeSchema.getSchema(tClazz);这一行空指针,也就是Class<T> tClazz = (Class<T>) GenericTypeResolver.resolveTypeArgument(this.getClass(), ProtoCommonDeserializer.class);没拿到Class信息,问了下豆包,豆包说出了那句我们都知道的八股文:
Java的泛型在运行时是擦除的,只有编译时有限制作用
而Spring的工具类GenericTypeResolver拿到的是运行时类信息,但是因为protoCommonDeserializer对象的类信息是ProtoCommonDeserializer,所以根本拿不到泛型参数,豆包给的解法是修改为
ProtoCommonDeserializer<Company> protoCommonDeserializer = new ProtoCommonDeserializer<Company>(){};注意,跟之前比,多了个花括号 ,也就是protoCommonDeserializer变成了一个匿名类的对象,而不再是ProtoCommonDeserializer的对象了,这个匿名类是ProtoCommonDeserializer<Company>的子类,且被load进jvm里了,所以该匿名类能查到的类的泛型参数
但是!!!
这样的话,protoCommonDeserializer就只能反序列化某一种类型的对象了
怎么办呢
如何实现一个反序列化对象可以通用,反序列化任何类型的对象呢?
参考ProtostuffSerializer写个ProtostuffDeserializer?
java
public class ProtostuffDeserializer implements Deserializer {
@Override
public Object deserialize(String topic, byte[] data) {
if (data == null) {
return null;
}
Schema schema = RuntimeSchema.getSchema(Object.class);
Company ans = new Company();
ProtostuffIOUtil.mergeFrom(data, ans, schema);
return ans;
}
}
跑个单测试试
java
@Test
public void testDeserialize1() {
String topic = "abc";
Company company = new Company("wangsansan", "not same place");
ProtostuffSerializer protostuffSerializer = new ProtostuffSerializer();
byte[] data = protostuffSerializer.serialize(topic, company);
ProtostuffDeserializer protostuffDeserializer = new ProtostuffDeserializer();
Company company1 = (Company) protostuffDeserializer.deserialize(topic, data);
assert "wangsansan".equals(company1.getName());
}
这次又是空指针,不过是protostuff框架报的了,其实是因为protostuff最外层的类信息是没有序列化到字节数组里,需要程序员指定?
何如?软件学的精髓是啥?封装,抽象呗,于是,我们再加一层
java
@NoArgsConstructor
@AllArgsConstructor
@Data
public class DomainWrapper<T> {
private T domain;
public static <T> DomainWrapper<T> of(T obj) {
return new DomainWrapper<>(obj);
}
}
写出了这样的通用反序列化类
java
public class ProtoWrapperDeserializer implements Deserializer<DomainWrapper> {
@Override
public DomainWrapper deserialize(String topic, byte[] data) {
if (data == null) {
return null;
}
Schema<DomainWrapper> schema = RuntimeSchema.getSchema(DomainWrapper.class);
DomainWrapper ans = new DomainWrapper();
ProtostuffIOUtil.mergeFrom(data, ans, schema);
return ans;
}
}
写个单测试试
java
@Test
public void testWrapperDeserialize() {
String topic = "abc";
Company company = new Company("wangsansan", "not same place");
User user = new User("wangsansan", 25);
ProtostuffSerializer protostuffSerializer = new ProtostuffSerializer();
byte[] data1 = protostuffSerializer.serialize(topic, DomainWrapper.of(company));
byte[] data2 = protostuffSerializer.serialize(topic, DomainWrapper.of(user));
ProtoWrapperDeserializer protoWrapperDeserializer = new ProtoWrapperDeserializer();
DomainWrapper<Company> obj1 = protoWrapperDeserializer.deserialize(topic, data1);
DomainWrapper<User> obj2 = protoWrapperDeserializer.deserialize(topic, data2);
assert obj1.getDomain().getName().equals("wangsansan");
assert obj2.getDomain().getAge() == 25;
}
执行是没问题的,因为protostuffSerializer对象的类信息是有泛型的->DomainWrapper,ProtoWrapperDeserializer类本身是不支持设置泛型的。
问题
虽然都是BeanWrapper,但是反序列化时,ProtostuffIOUtil.mergeFrom(data, ans, schema);是怎么知道里面的domain是什么类型,然后反序列化成功的呢?
这个问题,我们下次探讨一下。