梦回毕业季
今天跟儿子视频,突然看到自己的脸好像已经圆了,原来我已经过了三十岁了,中年发福。
敲下这几个字之时,不由回想这一路走来。我本非计算机科班出身,只是临毕业时随大流学了几个月java就出来"混"社会了。
当时连计算机概念什么的都不懂,更没有什么实战经验面试只会背"葵花宝典"。
一开始到一家外包公司实习,一块去的6个同学,三个月实用期不到,开了3个,其中就有我。那时既觉迷茫又觉丢脸,觉得自己技术不行(后来渐渐了解,这就是很多外包公司的营利手段,挣人头费)。还是同学好心收留我,让我住他租的房子,400一个月,一个单间,2个男人一张床,经常超市买一把面,一包几块钱的榨菜煮一大锅吃,甚至有一个月,差点连400的房租都交不起了,又不好意思找家里人开口,心里暗下发誓,毕业了坚决不依靠家里。
后来还是挺过来了,也不觉得很苦!
后来找到工作,第一家公司招我的项目经理三十来岁,头发已经白了,现在还是挺感激他的,算是把我领进了门,大家都叫他老邓。
言归正传,当时写代码一没经验,二是没有高要求,在类中,大家有的实现Serializable,有的不实现。有的声明serialVersionUID,有的也不声明serialVersionUID。
印象很深刻,当时有个好几年工作经验同事问老邓,这个serialVersionUID有什么用,老邓说是序列化用的。
当时单机系统,业务不复杂,也没有深究。
后来发现大家都认为很简单的东西,并不简单!以为知道的东西,其实并没有真正知道!
比如java序列化!
尽量显示声明序列化版本号serialVersionUID
在序列化的时候尽量声明serialVersionUID,不然可能踩坑
比如,有一个Person类,实现了Serializable,但是没有声明serialVersionUID
public class Person implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
现在写个测试用例,把它先序列化一个实例到person.out文件中,然后反序列化
public class SerializableTest {
@Test
public void test1() {
serialize();
deSerialize();
}
private static void serialize() {
// 创建一个对象
Person person = new Person();
person.setName("张三");
try {
// 序列化到文件中
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("person.out"));
outputStream.writeObject(person);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void deSerialize() {
try {
// 从文件中反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("person.out"));
Person person = (Person) inputStream.readObject();
inputStream.close();
System.out.println("=================name:" + person.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行之后,输出结果:=================name:张三
然后现在Person类中增加二个属性age,salary
public class Person implements Serializable {
private String name;
private int age;
private Long salary;
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 Long getSalary() {
return salary;
}
public void setSalary(Long salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
然后再重新反序列化一下:
@Test
public void test2() {
deSerialize();
}
运行之后,会报错,提示serialVersionUID版本不一致
"D:\Program Files\Java\jdk1.8.0_121\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:E:\progream\IDEA 2018.3.5\lib\idea_rt.jar=51082:E:\progream\IDEA 2018.3.5\bin" -Dfile.encoding=UTF-8 -classpath "E:\progream\IDEA 2018.3.5\lib\idea_rt.jar;E:\progream\IDEA 2018.3.5\plugins\junit\lib\junit-rt.jar;E:\progream\IDEA 2018.3.5\plugins\junit\lib\junit5-rt.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar;E:\work\code\threa-learn\threads\target\test-classes;E:\work\code\threa-learn\threads\target\classes;E:\work\qc\resposity\org\springframework\boot\spring-boot-starter\2.0.3.RELEASE\spring-boot-starter-2.0.3.RELEASE.jar;E:\work\qc\resposity\org\springframework\boot\spring-boot\2.0.3.RELEASE\spring-boot-2.0.3.RELEASE.jar;E:\work\qc\resposity\org\springframework\spring-context\5.0.7.RELEASE\spring-context-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\springframework\spring-aop\5.0.7.RELEASE\spring-aop-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\springframework\spring-beans\5.0.7.RELEASE\spring-beans-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\springframework\spring-expression\5.0.7.RELEASE\spring-expression-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\springframework\boot\spring-boot-autoconfigure\2.0.3.RELEASE\spring-boot-autoconfigure-2.0.3.RELEASE.jar;E:\work\qc\resposity\org\springframework\boot\spring-boot-starter-logging\2.0.3.RELEASE\spring-boot-starter-logging-2.0.3.RELEASE.jar;E:\work\qc\resposity\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\work\qc\resposity\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\work\qc\resposity\org\apache\logging\log4j\log4j-to-slf4j\2.10.0\log4j-to-slf4j-2.10.0.jar;E:\work\qc\resposity\org\apache\logging\log4j\log4j-api\2.10.0\log4j-api-2.10.0.jar;E:\work\qc\resposity\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;E:\work\qc\resposity\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;E:\work\qc\resposity\org\springframework\spring-core\5.0.7.RELEASE\spring-core-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\springframework\spring-jcl\5.0.7.RELEASE\spring-jcl-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\yaml\snakeyaml\1.19\snakeyaml-1.19.jar;E:\work\qc\resposity\org\springframework\boot\spring-boot-starter-test\2.0.3.RELEASE\spring-boot-starter-test-2.0.3.RELEASE.jar;E:\work\qc\resposity\org\springframework\boot\spring-boot-test\2.0.3.RELEASE\spring-boot-test-2.0.3.RELEASE.jar;E:\work\qc\resposity\org\springframework\boot\spring-boot-test-autoconfigure\2.0.3.RELEASE\spring-boot-test-autoconfigure-2.0.3.RELEASE.jar;E:\work\qc\resposity\com\jayway\jsonpath\json-path\2.4.0\json-path-2.4.0.jar;E:\work\qc\resposity\net\minidev\json-smart\2.3\json-smart-2.3.jar;E:\work\qc\resposity\net\minidev\accessors-smart\1.2\accessors-smart-1.2.jar;E:\work\qc\resposity\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;E:\work\qc\resposity\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;E:\work\qc\resposity\junit\junit\4.12\junit-4.12.jar;E:\work\qc\resposity\org\assertj\assertj-core\3.9.1\assertj-core-3.9.1.jar;E:\work\qc\resposity\org\mockito\mockito-core\2.15.0\mockito-core-2.15.0.jar;E:\work\qc\resposity\net\bytebuddy\byte-buddy\1.7.11\byte-buddy-1.7.11.jar;E:\work\qc\resposity\net\bytebuddy\byte-buddy-agent\1.7.11\byte-buddy-agent-1.7.11.jar;E:\work\qc\resposity\org\objenesis\objenesis\2.6\objenesis-2.6.jar;E:\work\qc\resposity\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;E:\work\qc\resposity\org\hamcrest\hamcrest-library\1.3\hamcrest-library-1.3.jar;E:\work\qc\resposity\org\skyscreamer\jsonassert\1.5.0\jsonassert-1.5.0.jar;E:\work\qc\resposity\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;E:\work\qc\resposity\org\springframework\spring-test\5.0.7.RELEASE\spring-test-5.0.7.RELEASE.jar;E:\work\qc\resposity\org\xmlunit\xmlunit-core\2.5.1\xmlunit-core-2.5.1.jar;E:\work\qc\resposity\com\alibaba\fastjson\1.2.4\fastjson-1.2.4.jar;E:\work\qc\resposity\com\caucho\hessian\4.0.60\hessian-4.0.60.jar;E:\work\qc\resposity\io\protostuff\protostuff-core\1.6.0\protostuff-core-1.6.0.jar;E:\work\qc\resposity\io\protostuff\protostuff-api\1.6.0\protostuff-api-1.6.0.jar;E:\work\qc\resposity\io\protostuff\protostuff-runtime\1.6.0\protostuff-runtime-1.6.0.jar;E:\work\qc\resposity\io\protostuff\protostuff-collectionschema\1.6.0\protostuff-collectionschema-1.6.0.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 serializa.SerializableTest,test2
java.io.InvalidClassException: com.example.serializa.Person; local class incompatible: stream classdesc serialVersionUID = -316107721330294453, local class serialVersionUID = 4471281140395852379
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at serializa.SerializableTest.deSerialize(SerializableTest.java:43)
at serializa.SerializableTest.test2(SerializableTest.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Process finished with exit code 0
所以:
- 如果实现了Serializable,尽量声明serialVersionUID,否则实体类属性变动(增加,删除,甚至第3方jar包里面的)会出现这种不兼容的情况
- idea插件安装方法,可以参考前面java序列化
Hessian序列化之坑
用hessian进行序列化也是很常见的,比如阿里的HSF
我们在声明一个类Teacher,继承Person
public class Teacher extends Person {
private String name;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
然后用hessian进行序列化及反序列化
@Test
public void test3() {
AbstractSerialize serialize = new HessianSerializeUtil();
Teacher teacher = new Teacher();
teacher.setName("李四");
// 序列化
byte[] bytes = serialize.serialize(teacher);
// 反序列化
teacher = serialize.deserialize(bytes, Teacher.class);
System.out.println(teacher);
}
输出的结果是什么呢?name字段为空
Teacher{name='null'}
真正的原因就是,Person和Teacher这2个父子类定义了一样的属性name,会导致只有子类被序列化,然后结果被父类的字段null覆盖
所以:
- 在用hessian序列化时,父类与子类不要定义名字一样的属性
- 或者hessian修复一下这个地方,属性反序列化的顺序反转下
jdk序列化之后改变类路径
前面介绍了一个线上bug,就是由于存储在缓存里面的对象序列化之后,改变了类路径,导致把序列化失败,可以查看java序列化2-再谈序列化
所以:
- 对于序列化的类,要千万注意包路径的变动都可能会引起反序列化失败
- 在设计及测试阶段一定要考虑兼容性,尤其是线上是集群或者分布式系统
- 对象先转成jsonString再进行序列化和反序列化操作,防止包路径改变导致的不兼容