一、背景
在系统功能开发过程中,如需要为Bean提供JSON序列化或反序列化的能力,当然这很简单,如下一行代码即可搞定
cn.hutool.json.JSONUtil.toJsonPrettyStr(Object)
当然这里的需求,实际是来自于JAVA与C++域的报文交互,需要通过特定方法进行二次处理。
考虑这里都是典型的模板代码,那是否可能参考lombok,让Bean的源码里自带类似toJson方法或fromString方法呢?
为此常规的方法有
- 硬编码,在每个Bean中增加该对应方法,或者在父类中统一定义
- 使用代理,比如对toString进行增强转成JSON输出
- 使用AOP,类似代理对象的实现方案
二、APT
除了以上几种方法,上文已经给出lombok参考,本文即探讨另一个基于APT的第四种方法,极底侵入代价的实现该方案。
APT(Annotation Processing Tool) 注解处理器,是 javac 的一个工具,它可以在源码生成class的时候,处理Java语法树。
开源工具lombok/mapStruct,包括云服务框架Micronaut中编译期注入核心原理也基于此
更多内容参考:https://blog.csdn.net/sunquan291/article/details/102635274

从JAVA编译的整个过程中,关键点在于注解处理器对于语法树的改造,这里也可能理解成JAVA提供的一个可以对源码进行修改的扩展点,下面我们利用该扩展点完成上节的功能点。
三、实现
第一需要定义一个注解,用于标识源码中需要进行相关字节码改写的判断入口,
如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JsonTrans {
}
第二即是字节码的改进逻辑,这块需要对于JCTree操作十分熟悉,更多操作细节需要专项学习。
package com.zte.sunquan.demo.sapi;
import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.zte.sunquan.demo.sapi.annotation.JsonTrans;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
@SupportedAnnotationTypes("com.zte.sunquan.demo.sapi.annotation.JsonTrans")
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class JsonTransProcessor extends AbstractProcessor {
/**
* 编译时期输入日志的
*/
private Messager messager;
/**
* 提供待处理的抽象语法树
*/
private JavacTrees javacTrees;
/**
* 封装了创建AST节点的一些方法
*/
private TreeMaker treeMaker;
/**
* 提供了创建标识符的方法
*/
private Names names;
private void note(String msg) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
this.messager = processingEnvironment.getMessager();
this.javacTrees = JavacTrees.instance(processingEnvironment);
Context context = ((JavacProcessingEnvironment) processingEnvironment).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
final JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils();
//获取使用注解标识的类
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(JsonTrans.class);
if (elements == null || elements.isEmpty()) {
return true;
}
for (Element element : elements) {
//遍历使用注解的类
JCTree tree = javacTrees.getTree(element);
genJsonTrans(tree);
}
return true;
}
private void genJsonTrans(JCTree tree) {
tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
//jcClassDecl.defs=必须
jcClassDecl.defs = jcClassDecl.defs.prepend(makeToJson(tree));
}
});
}
private JCTree.JCMethodDecl makeToJson(JCTree tree) {
// 方法级别
final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
// 方法名称
final Name methodName = names.fromString("toJson");
// 返回值类型
final JCTree.JCExpression returnType =memberAccess("java.lang.String");
// 没有参数类型
List<JCTree.JCTypeParameter> typeParameters = List.nil();
// 没有参数变量
List<JCTree.JCVariableDecl> params = List.nil();
// 没有异常
List<JCTree.JCExpression> throwClauses = List.nil();
//方法体JSONUtil.toJsonPrettyStr(this)
ListBuffer statements = new ListBuffer();
final JCTree.JCReturn toJsonStatement = treeMaker.Return(treeMaker.Apply(
//参数类型
List.of(memberAccess("java.lang.Object")),
memberAccess("cn.hutool.json.JSONUtil.toJsonPrettyStr"),
List.of(treeMaker.Ident(getNameFromString("this")))
));
statements.append(toJsonStatement);
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// 构造toJson
return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block, null);
}
private Name getNameFromString(String s) {
return names.fromString(s);
}
private JCTree.JCExpression memberAccess(String components) {
String[] componentArray = components.split("\\.");
JCTree.JCExpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
for (int i = 1; i < componentArray.length; i++) {
expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
}
return expr;
}
}
核心代码在方法makeToJson中。
四、验证
针对使用上述注解的Bean类
import com.zte.sunquan.demo.sapi.annotation.JsonTrans;
@JsonTrans
public class Bean {
......
编译后的class文件反编译后,结果如下,toJson方法已经增加

五、其它
META-INFO/service里文件生成 https://www.toutiao.com/article/7140955304656863748/
版权声明:本文为sunquan291原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。