使用 Lombok 遇到的坑

Lombok的主旨是提高开发效率,消除冗长的 JAVA 代码,尤其是 POJO 类型。在开发初期 POJO 经常修改,随之涉及到改动 getter/setter/toString 等一系列方法。虽然现在 IDE 都能自动生成对应代码,但还是需要手动删除重新生成。

它的原理是在编译期操作 AST(抽象语法树) 去改变字节码,根据注解生成对应的代码。因为在静态编译期做的,相对可控,使用也简单。

Google 开源的 AutoValue 也是用来解决这个问题,功能很全面,还可以定制化。但有一些的问题

  1. 代码侵入重,所有定义的对象是抽象类,使用都是封装后的 AutoValue_XXX
  2. 生成的 POJO 都是 immutable 的,需要频繁操作的类会很麻烦。
在使用 Lombok 期间遇到一个坑

当你的 POJO 中出现单字母驼峰名称时,例如:iType,Lombok生成出来的 getter/setter 方法

1
2
3
4
5
6
7
private String iType;
public String getIType() {
return iType;
}
public void setIType(String iType) {
this.iType = iType;
}

乍一看没啥问题,但是影响很严重,首先 Spring 是不认此方法的,其次也影响到 Jackson 的解析,直接导致无法使用

一般来说,代码规范和 API 规范有所区别,参考之前的文章「浅谈 Api 设计」。从后端输出 Api 时会做一些转换,例如驼峰转小写+下划线格式,这个 Jackson 有对应支持

1
2
3
4
5
6
Jackson2ObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)
//Jackson 也是通过 get 方法来获取属性
//我们的预期是
iType -> i_type
//而结果是
IType -> itype

开始我觉得这是 Lombok 的 bug,随后我下载了 Lombok 的源码来验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//HandlerUtil 类统一处理方法名
//可以看到规则是如果首字母小写,长度大于2并且第二个字母是大写或者是 titlecase 的话,则把首字母变大写。
/**
* @param prefix Something like {@code get} or {@code set} or {@code is}.
* @param suffix Something like {@code running}.
* @return prefix + smartly title-cased suffix. For example, {@code setRunning}.
*/
public static String buildAccessorName(String prefix, String suffix) {
if (suffix.length() == 0) return prefix;
if (prefix.length() == 0) return suffix;
char first = suffix.charAt(0);
if (Character.isLowerCase(first)) {
boolean useUpperCase = suffix.length() > 2 &&
(Character.isTitleCase(suffix.charAt(1)) || Character.isUpperCase(suffix.charAt(1)));
suffix = String.format("%s%s",
useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first),
suffix.subSequence(1, suffix.length()));
}
return String.format("%s%s", prefix, suffix);
}

显然这是特意而为之,随后我上官方 issue 中看了下,在15年已经有人提出了这个问题,而官方的答复是 Java Bean 的规范就是如此,且不应该使用首字母小写第二个字符大写的命名规则,Spring 的处理方式是自己发明的,我们不能跟着 Spring 乱搞,如果 Oracle 官方推荐如此或者所有人都是这么处理的话我们才改~

虽然我没有仔细的看过 Bean 规范,但如果 Spring 这么大厂都不支持的话会很难推广,在我刚和同事推荐的时候大家都非常激动,一听有这么个坑几乎都放弃了,也许是 xXxx 这种命名非常常见吧,比如 iPhone……

不光 Spring,Jackson,编译器( IDEA)自动生成的也是如此,只能说 Lombok 性格真是倔。既然如此只好自己修改下源码了…

1
2
3
4
5
//好在改动也非常小, 规则改为:长度大于2,并且第二个字母是大写则不转换大写
boolean useUpperCase = suffix.length() > 2 && Character.isUpperCase(suffix.charAt(1));
suffix = String.format("%s%s", useUpperCase ? first : Character.toTitleCase(first),
suffix.subSequence(1, suffix.length()));

PS:据官方说从 JAVA 讨论组数据统计,半数多人支持 getxXxx() 的做法。但他们大多数人的选择未必是正确的做法~

啊呸…