一、前言
在阿里编码规约中,有一个约定如下
【强制】POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列 化错误。
但为什么类中的field不能用is开头呢?本文将从问题演示、框架源码(本文使用的Jackson)两个方面来进行阐述。
二、问题演示
本文使用的Jackson,需要引入相关依赖,maven依赖如下,gradle配置请自行寻找。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
首先写一个简单的POJO,在这个POJO中,有三个属性,分别是isSuccess/code/msg,并自动生成了对应的get方法,这里要注意,isSuccess属性对应的get方法名也是isSuccess()。代码如下
public class Pojo {
private boolean isSuccess;
private int code;
private String msg;
private Pojo(boolean success, int code, String msg) {
this.isSuccess = success;
this.code = code;
this.msg = msg;
}
public static Pojo buildSuccess() {
return new Pojo(true, 2000, "success");
}
public boolean isSuccess() {
return isSuccess;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return "Pojo{" +
"isSuccess=" + isSuccess +
", code=" + code +
", msg='" + msg + '\'' +
'}';
}
}
然后再写一个类,在这个类中测试对POJO的序列化。在主函数中,会先创建一个POJO,然后将其打印,接着会使用Jackson中的ObjectMapper将对象序列化成Json字符串并打印。
public class Main {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
Pojo pojo = Pojo.buildSuccess();
System.out.println("raw pojo1: " + pojo.toString());
try {
System.out.println("serialize pojo1: " + mapper.writeValueAsString(pojo));
} catch (IOException e) {
System.out.println(e);
}
}
}
执行main()方法后,得到运行结果如下图:
可以发现,使用ObjectMapper对象将POJO序列化成String后,输出的变量名是success,而原有POJO中的变量isSuccess丢失。将该Json串反序列化成对象,会出现问题。
三、源码解析
笔者水平有限,源码看的浅薄,只讲解一下核心的流程。由于Jackson扩展性很好,对各种复杂情况都进行了处理,这里只讲解最普通的情况,比如,POJO无父类,fields都是private修饰,已经生成对应的getter方法等。
主要方法是POJOPropertiesCollector#collectAll(),该方法源码如下:
protected void collectAll()
{
LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();
// First: gather basic data
_addFields(props); // note: populates _fieldRenameMappings
_addMethods(props);
// 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
// inner classes, see [databind#1502]
if (!_classDef.isNonStaticInnerClass()) {
_addCreators(props);
}
// Remove ignored properties, first; this MUST precede annotation merging
// since logic relies on knowing exactly which accessor has which annotation
_removeUnwantedProperties(props);
// and then remove unneeded accessors (wrt read-only, read-write)
_removeUnwantedAccessor(props);
// Rename remaining properties
_renameProperties(props);
// and now add injectables, but taking care to avoid overlapping ones
// via creator and regular properties
_addInjectables(props);
// then merge annotations, to simplify further processing
// 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of
// annotations from getting properly merged
for (POJOPropertyBuilder property : props.values()) {
property.mergeAnnotations(_forSerialization);
}
// And use custom naming strategy, if applicable...
PropertyNamingStrategy naming = _findNamingStrategy();
if (naming != null) {
_renameUsing(props, naming);
}
// Sort by visibility (explicit over implicit); drop all but first of member
// type (getter, setter etc) if there is visibility difference
for (POJOPropertyBuilder property : props.values()) {
property.trimByVisibility();
}
// and, if required, apply wrapper name: note, MUST be done after
// annotations are merged.
if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {
_renameWithWrappers(props);
}
// well, almost last: there's still ordering...
_sortProperties(props);
_properties = props;
_collected = true;
}
collectAll()方法中,会调用自身的_addFields(Map<String, POJOPropertyBuilder> props)和_addMethods(Map<String, POJOPropertyBuilder> props),分别获取对象的fields和methods。
_addFields()方法最终会调用AnnotatedFieldCollector#_findFields(TypeResolutionContext tc, JavaType type, Map<String, FieldBuilder> fields),方法源码如下:
private Map<String,FieldBuilder> _findFields(TypeResolutionContext tc,
JavaType type, Map<String,FieldBuilder> fields)
{
// First, a quick test: we only care for regular classes (not interfaces,
//primitive types etc), except for Object.class. A simple check to rule out
// other cases is to see if there is a super class or not.
JavaType parent = type.getSuperClass();
if (parent == null) {
return fields;
}
final Class<?> cls = type.getRawClass();
// Let's add super-class' fields first, then ours.
fields = _findFields(new TypeResolutionContext.Basic(_typeFactory, parent.getBindings()),
parent, fields);
for (Field f : cls.getDeclaredFields()) {
// static fields not included (transients are at this point, filtered out later)
if (!_isIncludableField(f)) {
continue;
}
// Ok now: we can (and need) not filter out ignorable fields at this point; partly
// because mix-ins haven't been added, and partly because logic can be done
// when determining get/settability of the field.
if (fields == null) {
fields = new LinkedHashMap<>();
}
FieldBuilder b = new FieldBuilder(tc, f);
if (_collectAnnotations) {
b.annotations = collectAnnotations(b.annotations, f.getDeclaredAnnotations());
}
fields.put(f.getName(), b);
}
// And then... any mix-in overrides?
if (_mixInResolver != null) {
Class<?> mixin = _mixInResolver.findMixInClassFor(cls);
if (mixin != null) {
_addFieldMixIns(mixin, cls, fields);
}
}
return fields;
}
_findFields()方法中,getRawClass()获取Class信息,getDeclaredFields()反射获取所有的字段,并去除属于类的变量(即static修饰变量),最后按照方法堆栈顺序一步步将fields信息返回。_addFields()内获取到fields后进行遍历,进行一系列校验如是否有@JsonValue @JsonAnySetter,是否有隐式名、是否需要重命名、是否可见等,最后将校验后的field添加到POJOPropertyBuilder#_fields属性中
四、结论
通过上述论证,我们不难发现,类中的属性用is开头会导致对象序列化后产生意想不到的结果。所以在平时编码的过程中,我们不能在类变量中用is开头如: "isSuccess",这样容易造成程序错误。
如果情况特殊,也不必墨守成规,只是需要预先判断可能产生的影响、影响范围、能否监控等问题。更特殊的情况,你可以将变量名定义为isIsSuccess,或者将isSuccess定义为public。