阿里巴巴Java开发手册中的开发规约之一(1)

  |  

阿里巴巴Java开发手册中的开发规约之一

  • 【强制】POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误。
    • 反例: 定义为基本数据类型boolean isSuccess;的属性,他的方法也是isSuccess(), RPC框架在反向解析的时候,“以为”对应的属性名是success,导致属性获取不到,进而抛出异常

使用success 还是 isSucces?

到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。

为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Model1  {
private Boolean isSuccess;
public void setSuccess(Boolean success) {
isSuccess = success;
}
public Boolean getSuccess() {
return isSuccess;
}
}

class Model2 {
private Boolean success;
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}

class Model3 {
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
}

class Model4 {
private boolean success;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}

以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:

  • 基本类型自动生成的getter和setter方法,名称都是isXXX()setXXX()形式的。
  • 包装类型自动生成的getter和setter方法,名称都是getXXX()setXXX()形式的。

根据JavaBeans(TM) Specification规定,如果是普通的参数,命名为propertyName,需要通过以下方式定义其setter/getter:

1
2
public <PropertyType> get<PropertyName>();
public void set<PropertyName>(<PropertyType> a);

但是,布尔类型的变量propertyName则是另外一套命名原则的:

1
2
public boolean is<PropertyName>();
public void set<PropertyName>(boolean m);
  • 其实看到这里已经可以发现,当我们使用了基本变量isSuccess变量的时候,生成方法名是isSuccess()的,通过isSuccess()来获取变量的值的时候,方法会认为变量名是success,总而找不到值就会出现错误。下面就举例开发中会出现的一个错误

序列化带来的影响

我们这里拿比较常用的JSON序列化来举例,看看常用的fastJson、jackson和Gson之间有何区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BooleanMainTest {

public static void main(String[] args) throws IOException {
//定一个Model3类型
Model3 model3 = new Model3();
model3.setSuccess(true);

//使用fastjson(1.2.16)序列化model3成字符串并输出
System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));

//使用Gson(2.8.5)序列化model3成字符串并输出
Gson gson =new Gson();
System.out.println("Serializable Result With Gson :"+gson.toJson(model3));

//使用jackson(2.9.7)序列化model3成字符串并输出
ObjectMapper om = new ObjectMapper();
System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
}
}

class Model3 implements Serializable {

private static final long serialVersionUID = 1836697963736227954L;
private String kuanger;
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public String getKuanger(){
return "hello";
}
}
  • 输出

    1
    2
    3
    Serializable Result With fastjson :{"kuanger":"hello","success":true}
    Serializable Result With Gson :{"isSuccess":true}
    Serializable Result With jackson :{"success":true,"kuanger":"hello"}

我们可以得出结论:

  • fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getHollis和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性hollis和success的值。直接序列化成json:
1
{"kuanger":"hello","success":true}
  • 而Gson是通过反射遍历该类中的所有属性,并把其值序列化成json:

    1
    {"isSuccess":true}

可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。

  • 现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BooleanMainTest {
public static void main(String[] args) throws IOException {
Model3 model3 = new Model3();
model3.setSuccess(true);
Gson gson =new Gson();
System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
}
}

class Model3 implements Serializable {
private static final long serialVersionUID = 1836697963736227954L;
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
@Override
public String toString() {
return new StringJoiner(", ", Model3.class.getSimpleName() + "[","]")
.add("isSuccess=" + isSuccess)
.toString();
}
}
  • 以上代码的输出

    1
    Model3[isSuccess=false]

    这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为{"success":true}

    根据{"success":true}这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false

    但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题

    虽然也是主观规定,但这是阿里系的Java代码的惨痛的经验教训。这个可以说是在规范来规避团队中常用库的坑。这坑的解决方法是可以修改常用库里的逻辑来尽可能“聪明”地识别情况,也可以靠这样的规范。某种意义上说规范是从上游卡住了问题的发生,比起把下游处理弄复杂更要干净一些。

    所以,在定义POJO中的布尔类型的变量时,不要使用IsSuccess这种形式,而要直接使用success!

Copyright © 2018 - 2020 Kuanger All Rights Reserved.

访客数 : | 访问量 :