还有一次修改JAVA源码机会,要不要?

一、背景

在系统功能开发过程中,如需要为Bean提供JSON序列化或反序列化的能力,当然这很简单,如下一行代码即可搞定

cn.hutool.json.JSONUtil.toJsonPrettyStr(Object)

当然这里的需求,实际是来自于JAVA与C++域的报文交互,需要通过特定方法进行二次处理。

考虑这里都是典型的模板代码,那是否可能参考lombok,让Bean的源码里自带类似toJson方法或fromString方法呢?

为此常规的方法有

  1. 硬编码,在每个Bean中增加该对应方法,或者在父类中统一定义
  2. 使用代理,比如对toString进行增强转成JSON输出
  3. 使用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版权协议,转载请附上原文出处链接和本声明。