一、代码问题重现
想要使用json串在服务间传递信息时,我们会先定义一个POJO类,该类中包含是否删除属性isDeleted,代码如下
/** * @author wenxuan wang */
@Data public class ResultVO {
private boolean isDeleted;
private Integer errorCode;
private String errorMsg;
} |
构建出该类的对象并使用jackson将该对象序列化,结果如下:
{
"errorCode":101,
"errorMsg":"参数错误",
"deleted":false
}
isDeleted的is没有了。
二、追根溯源
ResultVO经过lombok的@Data注解自动生成getter,setter函数,class文件如下
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //
public class ResultVO { private boolean isDeleted; private Integer errorCode; private String errorMsg;
public ResultVO() { }
public boolean isDeleted() { return this.isDeleted; }
public Integer getErrorCode() { return this.errorCode; }
public String getErrorMsg() { return this.errorMsg; }
public void setDeleted(boolean isDeleted) { this.isDeleted = isDeleted; }
public void setErrorCode(Integer errorCode) { this.errorCode = errorCode; }
public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; }
public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof ResultVO)) { return false; } else { ResultVO other = (ResultVO)o; if (!other.canEqual(this)) { return false; } else if (this.isDeleted() != other.isDeleted()) { return false; } else { Object this$errorCode = this.getErrorCode(); Object other$errorCode = other.getErrorCode(); if (this$errorCode == null) { if (other$errorCode != null) { return false; } } else if (!this$errorCode.equals(other$errorCode)) { return false; }
Object this$errorMsg = this.getErrorMsg(); Object other$errorMsg = other.getErrorMsg(); if (this$errorMsg == null) { if (other$errorMsg != null) { return false; } } else if (!this$errorMsg.equals(other$errorMsg)) { return false; }
return true; } } }
protected boolean canEqual(Object other) { return other instanceof ResultVO; }
public int hashCode() { int PRIME = true; int result = 1; int result = result * 59 + (this.isDeleted() ? 79 : 97); Object $errorCode = this.getErrorCode(); result = result * 59 + ($errorCode == null ? 43 : $errorCode.hashCode()); Object $errorMsg = this.getErrorMsg(); result = result * 59 + ($errorMsg == null ? 43 : $errorMsg.hashCode()); return result; }
public String toString() { return "ResultVO(isDeleted=" + this.isDeleted() + ", errorCode=" + this.getErrorCode() + ", errorMsg=" + this.getErrorMsg() + ")"; } } |
可以看出isDeleted本来的get/set格式getIsDeleted/setIsDeleted,经过自动生成后变成isDeleted/setDeleted,万恶之源(编译器会对boolean类型的get/set进行优化)
三、源码解析
深入解析jackson的源码,可以发现如下代码
/** * @since 2.5 */ public static String okNameForRegularGetter(AnnotatedMethod am, String name, boolean stdNaming) { if (name.startsWith("get")) { /* 16-Feb-2009, tatu: To handle [JACKSON-53], need to block * CGLib-provided method "getCallbacks". Not sure of exact * safe criteria to get decent coverage without false matches; * but for now let's assume there's no reason to use any * such getter from CGLib. * But let's try this approach... */ if ("getCallbacks".equals(name)) { if (isCglibGetCallbacks(am)) { return null; } } else if ("getMetaClass".equals(name)) { // 30-Apr-2009, tatu: Need to suppress serialization of a cyclic reference if (isGroovyMetaClassGetter(am)) { return null; } } return stdNaming ? stdManglePropertyName(name, 3) : legacyManglePropertyName(name, 3); } return null; }
/** * @since 2.5 */ public static String okNameForIsGetter(AnnotatedMethod am, String name, boolean stdNaming) { if (name.startsWith("is")) { // plus, must return a boolean Class<?> rt = am.getRawType(); if (rt == Boolean.class || rt == Boolean.TYPE) { return stdNaming ? stdManglePropertyName(name, 2) : legacyManglePropertyName(name, 2); } } return null; }
/** * @since 2.5 */ @Deprecated // since 2.9, not used any more public static String okNameForSetter(AnnotatedMethod am, boolean stdNaming) { String name = okNameForMutator(am, "set", stdNaming); if ((name != null) // 26-Nov-2009, tatu: need to suppress this internal groovy method && (!"metaClass".equals(name) || !isGroovyMetaClassSetter(am))) { return name; } return null; }
/** * @since 2.5 */ public static String okNameForMutator(AnnotatedMethod am, String prefix, boolean stdNaming) { String name = am.getName(); if (name.startsWith(prefix)) { return stdNaming ? stdManglePropertyName(name, prefix.length()) : legacyManglePropertyName(name, prefix.length()); } return null; } |
jackson会根据VO的get/set函数来对VO进行序列化/反序列化。可以看出,如果是Boolean类型的变量,会将isDeleted的前缀is去掉,剩下的deleted作为反序列化key,而将setDeleted的前缀set去掉,剩下的deleted作为序列化key。
很多编译器的get/set的自动生成结果与标准结果有偏差,很多json序列化方法都使用和jackson相似的模式对序列化/反序列化过程进行优化,所以POJO 类中布尔类型最好不要用 isXxx 命名
就好像有一个残疾人,编译器也想给他加一只手,jackson也想给他加一只手,这个残疾人反而变得更不正常了