阿里巴巴开发手册中有这么一段话:
“POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误。反例:定义为基本数据类型boolean isSuccess 的属性,它的方法也是isSuccess(),RPC框架在反向解析的时候,以为对应的属性名称是success,导致属性获取不到,进而抛出异常”。
具体的理解如下:
通常基于Java的Web开发过程中不可避免的会遇到rpc服务调用,当接口中定义了一些布尔类型的变量的时候我们便会面临着这些变量该如何定义的问题。定义一个“是否成功”的布尔变量,我们有如下四种选择,其中setter与getter方法是Intellij IDEA 自动生成的:
public class BooleanNameTest1 {
private boolean success;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}
public class BooleanNameTest2 {
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
}
public class BooleanNameTest3 {
private Boolean success;
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
public class BooleanNameTest4 {
private Boolean isSuccess;
public Boolean getSuccess() {
return isSuccess;
}
public void setSuccess(Boolean success) {
isSuccess = success;
}
}
可以看出
- 基本类型自动生成的getter和setter方法,名称都是isXXX()和setXXX()形式的。
- 包装类型自动生成的getter和setter方法,名称都是getXXX()和setXXX()形式的。
对于包装类型与基础类型的选择,其中一点是考虑性能问题,举例说明:
基本类型需要1994ms,包装类型是14750ms。不过我们这里要分析的是基本类型,包装类型的定义便停留于此。
继续观察BooleanNameTest1和BooleanNameTest2发现虽然成员变量的命名不同,但是setter和getter方法却相同,都是isSuccess()和setSuccess()。
Java Bean中关于setter/getter的规范
关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据JavaBeans™ Specification规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:
public <PropertyType> get<PropertyName>();
public void set<PropertyName>(<PropertyType> a);
但布尔类型的变量propertyName则是单独定义的:
public boolean is<PropertyName>();
public void set<PropertyName>(boolean m);
通过对照这份JavaBeans规范,我们发现,在BooleanNameTest2 中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是一般IDE都会默认生成为isSuccess。
那这样做会带来什么问题呢。
在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候。
序列化带来的影响
我们这里拿比较常用的JSON序列化来举例,看看看常用的fastJson、jackson和Gson之间有何区别:
import java.io.IOException;
import java.io.Serializable;
import org.codehaus.jackson.map.ObjectMapper;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
/**
* @author zhang.yan
* @Time 2019/7/28.
*/
public class BooleanMainTest {
public static void main(String[] args) throws IOException {
//定一个Model类型
Model model = new Model();
model.setSuccess(true);
//使用fastjson序列化
System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model));
//使用Gson序列化
Gson gson =new Gson();
System.out.println("Serializable Result With Gson :" +gson.toJson(model));
//使用jackson序列化
ObjectMapper om = new ObjectMapper();
System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model));
}
}
class Model implements Serializable {
private static final long serialVersionUID = 1836697963736227954L;
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public String getExtra(){
return "extra";
}
}
运行结果如下:
"C:\Program Files (x86)\Java\jdk1.7.0_72\bin\java.exe" "-javaagent:D:\ideaSetting\IntelliJ IDEA 2018.2.3\lib\idea_rt.jar=3590:D:\ideaSetting\IntelliJ IDEA 2018.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\rt.jar;D:\MavenTest\target\classes;C:\Users\admin\.m2\repository\org\apache\poi\poi\4.1.0\poi-4.1.0.jar;C:\Users\admin\.m2\repository\commons-codec\commons-codec\1.12\commons-codec-1.12.jar;C:\Users\admin\.m2\repository\org\apache\commons\commons-collections4\4.3\commons-collections4-4.3.jar;C:\Users\admin\.m2\repository\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;C:\Users\admin\.m2\repository\org\apache\poi\poi-ooxml\3.14\poi-ooxml-3.14.jar;C:\Users\admin\.m2\repository\org\apache\poi\poi-ooxml-schemas\3.14\poi-ooxml-schemas-3.14.jar;C:\Users\admin\.m2\repository\org\apache\xmlbeans\xmlbeans\2.6.0\xmlbeans-2.6.0.jar;C:\Users\admin\.m2\repository\stax\stax-api\1.0.1\stax-api-1.0.1.jar;C:\Users\admin\.m2\repository\com\github\virtuald\curvesapi\1.03\curvesapi-1.03.jar;C:\Users\admin\.m2\repository\com\google\guava\guava\18.0\guava-18.0.jar;C:\Users\admin\.m2\repository\com\google\code\gson\gson\2.8.1\gson-2.8.1.jar;C:\Users\admin\.m2\repository\org\codehaus\jackson\jackson-mapper-asl\1.9.13\jackson-mapper-asl-1.9.13.jar;C:\Users\admin\.m2\repository\org\codehaus\jackson\jackson-core-asl\1.9.13\jackson-core-asl-1.9.13.jar;C:\Users\admin\.m2\repository\com\alibaba\fastjson\1.2.58\fastjson-1.2.58.jar" BooleanMainTest
Serializable Result With fastjson :{"extra":"extra","success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true,"extra":"extra"}
在fastjson和jackson的结果中,原来类中的isSuccess字段被序列化成success,并且其中还包含extra值。而Gson中只有isSuccess字段。
我们可以得出结论:fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getExtra和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性extra和success的值。直接序列化成json:{“extra”:“extra”,“success”:true}
但是Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json:{“isSuccess”:true}
可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。
前面提到的关于对getExtra的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同,我们暂且把他放到一边,我们把他从Model中删除后,重新执行下以上代码,得到关于success结果与以上相同。
现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.Serializable;
/**
* @author zhang.yan
* @Time 2019/7/28.
*/
public class BooleanMainTest {
public static void main(String[] args) throws IOException {
Model model = new Model();
model.setSuccess(true);
Gson gson =new Gson();
System.out.println(gson.fromJson(JSON.toJSONString(model),Model.class));
}
}
class Model implements Serializable {
private static final long serialVersionUID = 1836697963736227954L;
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public String toString() {
return String.valueOf(isSuccess);
}
}
运行结果
"C:\Program Files (x86)\Java\jdk1.7.0_72\bin\java.exe" "-javaagent:D:\ideaSetting\IntelliJ IDEA 2018.2.3\lib\idea_rt.jar=4875:D:\ideaSetting\IntelliJ IDEA 2018.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.7.0_72\jre\lib\rt.jar;D:\MavenTest\target\classes;C:\Users\admin\.m2\repository\org\apache\poi\poi\4.1.0\poi-4.1.0.jar;C:\Users\admin\.m2\repository\commons-codec\commons-codec\1.12\commons-codec-1.12.jar;C:\Users\admin\.m2\repository\org\apache\commons\commons-collections4\4.3\commons-collections4-4.3.jar;C:\Users\admin\.m2\repository\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;C:\Users\admin\.m2\repository\org\apache\poi\poi-ooxml\3.14\poi-ooxml-3.14.jar;C:\Users\admin\.m2\repository\org\apache\poi\poi-ooxml-schemas\3.14\poi-ooxml-schemas-3.14.jar;C:\Users\admin\.m2\repository\org\apache\xmlbeans\xmlbeans\2.6.0\xmlbeans-2.6.0.jar;C:\Users\admin\.m2\repository\stax\stax-api\1.0.1\stax-api-1.0.1.jar;C:\Users\admin\.m2\repository\com\github\virtuald\curvesapi\1.03\curvesapi-1.03.jar;C:\Users\admin\.m2\repository\com\google\guava\guava\18.0\guava-18.0.jar;C:\Users\admin\.m2\repository\com\google\code\gson\gson\2.8.1\gson-2.8.1.jar;C:\Users\admin\.m2\repository\org\codehaus\jackson\jackson-mapper-asl\1.9.13\jackson-mapper-asl-1.9.13.jar;C:\Users\admin\.m2\repository\org\codehaus\jackson\jackson-core-asl\1.9.13\jackson-core-asl-1.9.13.jar;C:\Users\admin\.m2\repository\com\alibaba\fastjson\1.2.58\fastjson-1.2.58.jar;C:\Users\admin\.m2\repository\org\mockito\mockito-core\1.9.5\mockito-core-1.9.5.jar;C:\Users\admin\.m2\repository\org\hamcrest\hamcrest-core\1.1\hamcrest-core-1.1.jar;C:\Users\admin\.m2\repository\org\objenesis\objenesis\1.0\objenesis-1.0.jar" BooleanMainTest
false
Process finished with exit code 0
这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化成字符串后内容为{“success”:true}。
根据{“success”:true}这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false。
但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题。
所以,作为开发者,我们应该想办法尽量避免这种问题的发生,对于POJO的设计者来说,只需要做简单的一件事就可以解决这个问题了,那就是把isSuccess改为success。这样,该类里面的成员变量时success,getter方法是isSuccess,这是完全符合JavaBeans规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。