写点什么

当匈牙利命名法遇到 JAVA 会怎么样?

用户头像
Java架构师
关注
发布于: 2021 年 06 月 04 日

先点赞再看,养成好习惯

背景

之前在做一个老项目重构的时候,由于数据库不能改动,所以还是继续沿用之前的老数据库。保险公司嘛,哪怕加了个互联网保险的 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 获取,配置方式见上面的补充部分

用户头像

Java架构师

关注

还未添加个人签名 2021.05.28 加入

还未添加个人简介

评论

发布
暂无评论
当匈牙利命名法遇到 JAVA 会怎么样?