基于生成式注解为类添加toString方法

在组内讨论时,有同事提建议在把对象写到日志中时最好直接输出对象不要做任何加工,也就是尽量调用对象自己的toString()方法,不要用JsonKit.toJson(obj)这样先把对象转为json字符串再输出的写法。

这个建议不是没有道理的,jackson和fastjson这些json工具将对象序列化为字符串时会有一个自动检查推测的过程,在这个过程中做了如下事情:

  1. 所有public方法,带返回值,符合“getXxx”(或“isXxx”,如果返回boolean会被称为“isgetter”)命名约定的成员方法被推测存在名字为“xxx”的属性(属性名按照bean命名约定推测,即开头大写字母转成小写)。
  2. 所有public成员字段被推测为要显示的属性,使用字段名字来序列化。

也就是说,一个“getXxx()”方法中如果做了业务性的处理,在被调用的过程中也会被执行。如下面的伪代码:

在对FetchUserAction的实例进行序列化时,会得到下面的json:

得到这个json的时候意味着至少已经做了 “从应用上下文获得当前用户ID” 和 “从数据库查询用户信息” 两个动作。在输出日志的时候静默的执行了一次涉及到资源的操作,这不是一个合理的事情。

当然也可以为getCurrentUser()这个方法添加类似@JsonIgnore这样的注解以避免出现上面的情况。但问题不在这里,问题的关键在于我们应该只需要对model类的实例或其对应的集合做toJson的处理;其它的执行业务处理的对象不应该被输出到日志中,即使因为种种原因不得不将之输出到日志中也不应该做toJson的处理,以避免出现类似前面的例子中的情况。

如果model类的toString()方法的返回值就已经是经过json序列化的就好了,这样我们在输出日志时就不需要显式地再做这个toJson的操作了,也就不会误将不需要json序列化的对象给序列化了。我一开始想的是通过lombok来解决这个问题,因为model类一般是依赖lombok来生成toString方法的。不幸的是,经过调研,我发现虽然已经有人在lombok的相关issue里提过类似的问题,但是lombok现阶段还不支持这么做。要想解决就只能自己实现了。

期望实现的效果是能够根据类上的一个注解如@ToJson来在编译期自动生成类的toString方法。方法内容是下面这样的:

在toString方法中调用json序列化工具类实现了将当前对象转为json字符串的操作。

最开始我是想用bytebuddy或asm来做这个事情的,但是使用这些字节码工具的时候需要加上javaagent相关的配置,运维肯定是不允许的。后来我又接触到了生成式注解,感觉这应该是解决这个问题的一个出路。

先来看下什么是生成式注解:

生成式注解处理器是JSR-269中定义的API,该API可以在编译期对代码中的特定注解进行处理,从而影响到前端编译器的工作过程,通过生成式注解处理器可以读取、修改、添加抽象语法树中的任意元素。

“可以修改添加抽象语法树中的任意元素”,这看起来很酷,正是我想要的。

关于如何修改语法树可以参考这篇文档:《Java 中的屠龙之术:如何修改语法树?》。

看下具体是如何实现的吧,在类中添加toString方法的代码如下:

上面这个方法实现了向类中添加 toString 方法的逻辑,注释是我用通义灵码生成的,还是挺精准的。 如通义生成的注释说明, makeToStringBody 负责生成toString 方法的具体逻辑,这个方法的逻辑如下:

核心代码就是这些。其他的代码可以看我这个项目 zhyea / lombok-ext 。

本来还可以展开说说lombok和mapstruct的,但就这样吧!

END!!!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据