最近在开发过程中遇到一个超预期的问题,经过详细调试研究后找到了问题根源,现形成文字以示众人,方便遇到此问题的同仁们快速过坑。
问题现象
问题发生的场景是这样的,有个查询方法从DB中查询出数据缓存到Redis中。方法返回的的Pojo对象,那么在存Redis时,就要进行序列化,此时应用了FastJson。当在缓存进Redis时一切正常,当我第二次查询时,当然去Redis中取出序列化的数据还原成我想要的Pojo类,在这时FastJson发生了异常,见下图:
从异常字面上看是未找到默认构造器,于是乎我给Service实现类加了个无参构造器以验证这一异常,结果是没有生效,依然报上图的异常。我只是期望把对象正常的存取,何必让我做些不相关的事务呢,难道我每创建一个对象就必须重写构造器吗,这不合理呀,我太不自由了,而且我创建了构造器也没有生效。于是乎写下代码模拟发生的问题,深究其发生的原因。
创建User类
package doublebraceinit;
public class User {
private String name = "";
private String sex = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void print(){
System.out.printf("name:%s,age:%s\n", this.name, this.sex);
}
}
创建Role类
package doublebraceinit;
public class Role {
private String code = "";
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
创建UserTest类
package doublebraceinit;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.gzsw.common.exception.BusiException;
import org.gzsw.modules.api.config.EtaxConfig;
import org.gzsw.modules.api.entity.Region;
import org.gzsw.modules.api.mapper.RegionMapper;
import org.gzsw.modules.api.service.IRegionService;
import org.gzsw.modules.api.vo.RegionTreeVO;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class UserTest {
private Role role;
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public static void main(String[] args){
User zhangsan = new User();
zhangsan.setName("张三");
zhangsan.setSex("男");
zhangsan.print();
String zhangsanJson = JSON.toJSONString(zhangsan, SerializerFeature.WriteClassName);
System.out.println("zhangsanJson = " + zhangsanJson);
User lisi = new User(){{
setName("李四");
setSex("女");
}};
lisi.print();
String lisiJson = JSON.toJSONString(lisi, SerializerFeature.WriteClassName);
System.out.println("lisiJson = " + lisiJson);
}
}
这里用FastJosn转成字符串时,要用SerializerFeature.WriteClassName模式,因为我们要保留类类型,期望反序列化时转回原类型。
问题根源
我们执行一个main方法,结果见下图:
看到了差异:
在未使用匿名内部类初始化时,序列化后的字符串中类类型是正确的类型,即User;
在使用了匿名内部类初始化时,序列化后的字符串中类类型是一个继续了原类类型的匿名内部类,即UserTest$1。
虽然在大部分情况下能够还原成User对象,毕竟UserTest$1继承于User。但是在有些情况下会发生错误,比如在我的项目中,在Service层实现类中使用了此方式,导致了本文前面出现的异常。于是我们得出的结论是用匿名内部类初始化的对象,并用FastJson序列化时会发生序列化问题,原类类型序列化错误。
最后忠告
不建议使用匿名内部类初始化法,一是性能问题,二是序列化问题。