共计 3695 个字符,预计需要花费 10 分钟才能阅读完成。
先点赞再看,养成好习惯
背景
之前在做一个老我的项目重构的时候,因为数据库不能改变,所以还是持续沿用之前的老数据库。保险公司嘛,哪怕加了个互联网保险的 title,业务和零碎还是偏传统的,数据模型不会轻易的更新;所以这个零碎年代比拟长远,而且它的数据库表命名形式采纳的还是 匈牙利命名法,导致在重构时因为这个命名形式恶心了我良久……
匈牙利命名法
匈牙利命名法(Hungarian notation),由 1972 年至 1981 年在施乐帕洛阿尔托钻研核心工作的 - 程序员 查尔斯·西蒙尼创造,这位前辈前面 成了微软的总设计师。
这个命名法的特点是,在命名后面减少 类型的前缀,就像这样:
- c_name – 姓名,字符串(Char)类型
- n_age – 年龄,数字(Number)类型
- t_birthday – 生日,日期 / 工夫(Time)类型
可不要小看这个命名法,当年可是很风行的,而且直到明天还是有一些零碎依然在沿用这个命名规范,比方微软的 Win32 API:
况且这个命名法,也不是一无是处,还是有肯定的长处的,至多我一眼就能够看出这个字段的类型。
只不过在明天看起来有点怪怪的,不太合乎当今的设计格调,如果放到人名上就更有意思了:
- 男 赵四
- 女 谢大脚
- 男 刘能
当匈牙利命名法遇到 JAVA
咱们这次的重构指标,是要放弃老零碎表不动的状况下,齐全重写。可是新零碎是 Java 语言来开发,Java 可是驼峰命名规范的,当这个匈牙利命名法的表迁徙到驼峰命名法的 Java 语言会怎么样?
比方 c_name 这个命名,到 Java 里之后,是改为 CName 呢,还是 cName 呢?如同怎么都有点奇怪,不过最初咱们还是抉择了 CName,将类型的前缀齐全大写,至多看着略微失常一点,没那么反人类
- c_name -> CName
- n_age -> NAge
- t_birthday -> TBirthday
序列化的问题
刚确定了命名形式,还没开心多久,我就遇到了一个十分好受的问题……
因为是 Spring 全家桶,Web 层应用的也是 Spring MVC。Spring MVC 的默认 JSON 解决库是 Jackson,在 Web 层返回 JSON 后,数据就成了这个样子:
{
"nid":1,
"ctitle":"Effective JAVA"
}
可我这个 POJO 类是将匈牙利命名法的字段转了大写,它长这样啊:
public class Book {
private Integer NId;
private String CTitle;
public Integer getNId() {return NId;}
public void setNId(Integer NId) {this.NId = NId;}
public String getCTitle() {return CTitle;}
public void setCTitle(String CTitle) {this.CTitle = CTitle;}
}
大写字段名,在转 JSON 之后变成了小写……要是把这个小写的字段给了前端,前端命名必定会用小写,那前端在发送到后端时后肯定也是小写。
那因为咱们出入参序列化都是 Jackson,对于 Jackson 来说,怎么出就怎么进,还是能解析进去的,看似也没啥问题,只是恶心了一点,前后端一个大写一个小写。
不过……事件并没有那么简略。后端不会将所有的报文都作为 POJO 的字段,会存在一些动静的字段,用 Map 存储。可 Map 存储的这些字段,Jackson 在输入时不会转为小写,还是保留原有的大写模式,这样就会导致给前端的字段,尽管都是匈牙利格调,但有些大写有些小写……尽管前端不晓得打不打人,但我可不敢这么玩
不同序列化库的解决机制不同
Jackson 的匈牙利命名法解决
其实 Jackson 序列化之后转小写的起因也很好解释,Java 的设计规范,就是 Bean 中的属性用 private 润饰,而后提供 getter/setter 来提供读取 / 写入。那么在序列化时,Jackson 通过字段的 Getter 来拜访属性值,甚至用 Getter 办法来解析属性名。
Java 的 getter 办法命名规范是,将小写驼峰转大写驼峰,Jackson 在通过 getter 办法名解析字段名时,将 getNID 解析为 nid 了,所以导致最终输入的字段名为小写的 nid。
FastJson 的匈牙利命名法解决
原本是想定制化一下 Jackson 的命名解决的,但想了一下感觉没必要,毕竟是咱们命名不规范,何必去改这个命名解决机制呢,划不来。
所以我又尝试着换一种 JSON 库去解决这个命名问题,先试试阿里的 FastJSON,看看这个国产库的解决怎么样:
{
"cTitle":"Effective JAVA",
"nId":1
}
看到这个序列化后果时,我差点把我键盘上的 Backspace 按断了……同样是通过 getter 办法解析属性名,两个库的解析规定还能不一样……
在 FastJson 里,c 是小写了,可 Title 里的 T 还是大写,@#¥%……& 此处省略 100 字……
沉着一下之后,心里默念了几遍:“不怪他人,是咱们本人的命名问题,不符合标准人家怎么解析都不关你事……”
不过 JAVA 的生态这么好,JSON 库也不止这两个,再换一个就是,Google 的 Gson 也很不错嘛!
Gson 的匈牙利命名法
于是我又换成了 Gson,配置完 Spring MVC Gson Converter 之后,输入后果:
{
"NId":1,
"CTitle":"Effective JAVA"
}
终于换到一个能失常显示原始字段名的 JSON 库了,不过它既然能放弃原有字段名,而不是 getter 里解析的属性名,那么它必定不是解析 getter 办法名的
于是我又去翻了下 Gson 的源码,发现它是间接 getDeclaredFields()
,而后 makeAccessible
,最初间接通过 Field.getValue
的形式间接获取属性值的。
相比 Jackson 和 FastJson 里通过 getter 获取属性列表,而后通过调用 getter 办法来获取属性值的办法来说,强制拜访公有属性这种做法还是太暴力了,不过我喜爱,至多它能轻松解决了我的问题
其余的序列化问题
除了 JSON 这种文本模式的序列化之外,一些二进制的序列化也会有这个难堪的问题,获取属性列表 / 属性值,到底是用解析 getter 办法的形式,还是间接 makeAccessible 暴力拜访公有属性呢?
这个我测试了一下,比方在 Dubbo 的默认序列化形式(Dubbo 简化的 Hession2)中,依然是 getDeclaredFields
,而后拜访公有属性
在 JDK 的内置序列化 ObjectOutputStream 中,也是 getDeclaredFields
,而后拜访公有属性。
不过这种 getDeclaredFields,而后拜访公有属性值的形式,也会有一些劣势。比方在遇到代码混同时,公有属性的值会被全副打乱,而 public 的办法却不会,所以在遇到混同的代码时,这种形式就会乱套了,而通过 getter 办法解析的形式就不会有问题。
所以吧,这个获取属性的形式并没有对错,怎么都能够,不过我认为还是应该通过 getter/setter 的形式来操作,合乎 JAVA 的标准。
补充
感激 @用户 3323102545477 的揭示,Jackson 很弱小,反对配置属性的获取形式,能够配置 Visibility
来实现只通过 Field
而不通过 getter
来获取属性:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE)
public class Book {
private Integer NId = 1;
private String CName = "John";
}
全局配置更不便:
ObjectMapper objectMapper = new ObjectMapper();
// 配置 field 为可见
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
// 配置 getter 不可见
objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
总结
Java 里拜访公有属性值,规范的形式是通过 getter 办法,但还是提供了一个 makeAccessible 操作,能够让咱们间接拜访公有属性或者公有办法。
始终不太明确 JDK 为什么要这么设计,既然曾经指定了标准,为什么还要开个后门呢?如果限度死了这个性能,那么所有序列化的库不就能够对立了,再也没有这种恶心的不统一问题!
但比照以上三个序列化库,我感觉都没错,Jackson/FastJson 依照标准的形式来,老老实实的通过 getter 办法来获取,而 Gson 就有点暴力,间接拜访公有属性,各有劣势。
补充:Jackson 反对属性的获取形式,默认是通过 getter
获取,但也能够配置通过 Field
获取,配置形式见下面的 补充 局部