Annotation Processor

文章介绍了Java的AnnotationProcessor如何在编译时根据注解修改类文件,如Lombok,重点展示了如何通过`InsertField`注解在不直接修改源码的情况下,向类中添加新域的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Annotation Processor

Processor处理约定

  1. JavaC编译环境获取当前的源文件及类文件, 建立多轮次的处理过程。每一次轮次的处理结果将作为下一轮的输入。当某一轮处理完成后没有产生新的源文件或类文件,触发最后一轮。
  2. Processors 通过getSupportedAnnotationTypes、 getSupportedOptions、 getSupportedSourceVersion 方法确定是否匹配需要处理的Annotation.
  3. Processor的process方法返回值: false表明匹配注解没有被processor正确处理, 对应的注解将继续寻找合适的processor处理, 返回true表明已经被处理,对应的注解在本轮次将不再交给别的processor处理。 Process方法如果抛出未捕获的异常,将中断编译过程。

Processor 接口方法

/**

 *识别和匹配注解的@SupportedOptions配置

 */

Set<String> getSupportedOptions();

/**

 *返回processor匹配的注解类型

 */

Set<String> getSupportedAnnotationTypes();

/**

 *返回process支持的最新源码版本

 */

SourceVersion getSupportedSourceVersion();

/**

 *由执行环境调用对processor进行初始化

 */

void init(ProcessingEnvironment processingEnv);

/**

 *注解处理方法

 */

boolean process(Set<? extends TypeElement> annotations,

                RoundEnvironment roundEnv);

抽象语法树(AST)

Annotation Processor目前所常见的场景就是编译时根据特定注解改造最终生成的类文件, 如lombok。 相应功能离不开JAVA 抽象语法树(AST)的概念及相关接口。AST 是JAVA语法的抽象, 编译过程中源文件的Java语法将解析为AST并提供相应操作接口进行读取和修改。

com.sun.source.tree.Tree接口: 语法树的基础接口, 所有的语法块均实现该接口,提供访问者模式接口:<R, D> R accept(TreeVisitor<R, D> var1, D var2);

com.sun.source.util.Trees类: 语法树工具类, 提供了获取语法树相关Tree对象的方法。使用的实现类JavacTrees

Tree.Kind 枚举: 定义了语法块的类型。如 ANNOTATION(AnnotationTree.class),

JCTree: Tree接口的抽象实现类, 其他具体类型集成实现该类。

TreeTranslator:JCTree的访问者接口实现。 Processor实现中实现该类的子类,覆盖注解目标对应类型的visit方法,如访问类结构的visitClassDef(JCClassDecl jcClassDecl)方法。

Processor示例-样例

考虑一个问题: 如何往一个不能直接修改源码的类里面增加域? 比如接口协议对象作为一个规范,不允许进行源码修改特定类型。但要进行扩展,增加参数。

当然答案有很多,比如 通过替换同名的类, 通过Spring的FactoryBean等扩展点替换掉类定义等。这里说明如何通过Annotation processor的方式实现。

需要做的工作包括:

  1. 定义一个注解,该注解作用在类上, 作用范围是源码。
  2. 在使用该注解的类上增加需要添加的域, 并且通过某种方式指明要讲新的域加入的目标类型。 这里有不同的实现方式,比如可以再注解中直接指明目标类型,示例使用集成的方式说明新的域要加入到父类中,这样保持类型语义的连续性。
  3. 实现Annotation Processor, 对新增了注解的类进行解析,将新增注解类中的域添加到父类中(重新父类的类文件)。

关键代码如下:

@Retention(RetentionPolicy.SOURCE)

@Target({ElementType.TYPE})

public @interface InsertField {

}

public synchronized void init(ProcessingEnvironment processingEnv) {

         //messager用于打印日记

    this.messager = processingEnv.getMessager();

    //获取Trees对象,作为Tree语法块的获取入口

    this.trees = JavacTrees.instance(processingEnv);

}

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    try {

        this.messager.printMessage(Kind.NOTE, "start to process extend POJO");

        //获取打了InsertField注解的类

        Iterator insertFieldClassIterator = roundEnv.getElementsAnnotatedWith(InsertField.class).iterator();

        while(insertFieldClassIterator.hasNext()) {

            Element element = (Element)insertFieldClassIterator.next();

            JCTree jcTree = this.trees.getTree(element);

            jcTree.accept(new TreeTranslator() {

                public void visitClassDef(JCClassDecl jcClassDecl) {

                    try {

                        ClassPool cp = ClassPool.getDefault();

                        //获取其继承的父类,需要将域插入到父类中

                        String targetClassName = ((JCIdent)jcClassDecl.extending).sym.toString();

                        targetClassName = StringUtils.replace(targetClassName, ".", "/")+ ".class";

                        //读入要修改的类

                        InputStream targetClassStream = this.getClass().getClassLoader().getResourceAsStream(targetClassName);

                        if (targetClassStream == null) {

                            this.messager.printMessage(Kind.WARNING, "非标准类, 无法插入");

                        } else {

                            //生成CtClass对象 用于插入新增的域及方法

                            CtClass targetClass = cp.makeClass(targetClassStream);

                            //遍历注解类的定义,获取域

                            List<JCVariableDecl> jcVariableDeclList = List.nil();

                            Iterator defsIterator = jcClassDecl.defs.iterator();

                            while(defsIterator.hasNext()) {

                                JCTree tree = (JCTree)defsIterator.next();

                                if (tree.getKind().equals(com.sun.source.tree.Tree.Kind.VARIABLE)) {

                                    JCVariableDecl jcVariableDecl = (JCVariableDecl)tree;

                                    Set<Modifier> flags = jcVariableDecl.mods.getFlags();

                                    if (!flags.contains(Modifier.FINAL) && !flags.contains(Modifier.STATIC)) {

                                        jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);

                                    }

                                }

                            }

                            var7 = jcVariableDeclList.iterator();

                            while(var7.hasNext()) {

                                JCVariableDecl jcVariableDeclx = (JCVariableDecl)var7.next();

                                ExtendPOJOProcessor.this.messager.printMessage(Kind.NOTE, jcVariableDeclx.getName() + " has been processed");

                                String fieldType = jcVariableDeclx.vartype.type.tsym.toString();

                                String fieldName = (new StringBuffer()).append("private ").append(fieldType).append(" ").append(jcVariableDeclx.getName()).append(";").toString();

                                if (cp.getOrNull(fieldType) == null) {

                                    cp.makeClass(fieldType);

                                }

                                //创建新增的域

                                CtField ctField = CtField.make(fieldName, targetClass);

                                ConstPool constPool = targetClass.getClassFile().getConstPool();                  

                                String recapitalizedName = org.springframework.util.StringUtils.capitalize(ctField.getName());

                                String setterMethodName = "set" + recapitalizedName;

                                String getterMethodName = "get" + recapitalizedName;

                                 //创建新增域的get/set方法

                                CtMethod fieldSetter = CtNewMethod.setter(setterMethodName, ctField);

                                CtMethod fieldGetter = CtNewMethod.getter(getterMethodName, ctField);

                                //设置方法的signature

                                List<Type> types = jcVariableDeclx.vartype.type.getTypeArguments();

                                if (CollectionUtils.isNotEmpty(types)) {

                                    StringBuilder signatureBuilder = (new StringBuilder("L")).append(Descriptor.toJvmName(jcVariableDeclx.vartype.type.tsym.toString())).append("<");

                                    Iterator typeIterator = types.iterator();

                                    while(typeIterator.hasNext()) {

                                        Type type = (Type)typeIterator.next();

                                        signatureBuilder.append(Descriptor.of(type.toString()));

                                    }

                                    signatureBuilder.append(">;");

                                    ctField.setGenericSignature(signatureBuilder.toString());

                                    fieldSetter.setGenericSignature("(" + signatureBuilder + ")V");

                                    fieldGetter.setGenericSignature("()" + signatureBuilder);

                                }

                                //将域加入targetClass

                                targetClass.addField(ctField);

                                //将域的getter方法加入targetClass

                                targetClass.addMethod(fieldSetter);

                                //将域的setter方法加入targetClass

                                targetClass.addMethod(fieldGetter);

                            }

                            //重写要插入域的类文件

                            String classPath = this.getClass().getResource("/").getPath();

                            targetClass.writeFile(classPath);

                            targetClass.defrost();

                            super.visitClassDef(jcClassDecl);

                        }

                    } catch (Throwable ex1) {

                        throw ex1;

                    }

                }

            });

        }

        return true;

    } catch (Throwable ex2) {

        throw ex2;

    }

}

在resources/META-INF/services路径下新建javax.annotation.processing.Processor文件,写入Processor实现类:

com.demo.annotationprocess.InsertFieldProcessor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值