• Java注解与自己定义注解处理器


    动机

    近期在看ButterKnife源代码的时候。竟然发现有一个类叫做AbstractProcessor,并且ButterKnife的View绑定不是依靠反射来实现的,而是使用了编译时的注解,自己主动生成的.class文件。

    真是一个奇妙的东西啊!

    所以本文就注解与自己定义的注解处理器来学习注解。项目Github地址

    基础知识

    大家应该知道元注解@Retention吧,它表示注解在什么时候存在,能够分为3个时期:

    • RetentionPolicy.SOURCE:表示注解仅仅在java文件中面才有,在编译的时候。这部分注解就会被擦出。相似于@Override仅仅是给程序猿看的。

    • RetentionPolicy.CLASS:注解在编译的时候会存在。在执行的时候就会擦除。
    • RetentionPolicy.RUNTIME:在执行的时候会存在。这里就要用到了反射来实现了。

    Annotation Processor

    注解处理器。在编译期间,JVM会自己主动执行注解处理器(当然。我们须要将其注冊)。尽管我们写的Java代码被编译成class就不能被修改了,可是注解处理器会又一次生成其它的java代码。我们能够通过反射来调用新生成的java文件中面的类或方法。然后JVM再对这些生成的java代码进行编译。

    这是一个递归的过程。

    Java API为我们提供了注解处理器的接口。我们仅仅要实现它就能够了。这个类就是AbstractProcessor,以下就一起来实现它。这个样例我是从【Java二十周年】Java注解处理器,參考过来的。

    实现功能例如以下:对于一个类,仅仅要给它的某个加上一个注解,就会自己主动生成其接口。接口中的方法就是加注解的方法。

    public class Man {
    
        @Interface("ManInterface")
        public void eat() {
            System.out.println("Eat");
        }
    }

    eat方法加上了@Interface("ManInterface"),上述就会生成:

    public interface ManInterface {
      void eat();
    }
    

    就是这么粗暴。


    然后我们看看注解是怎么定义的:

    package wqh.core;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * Created on 2016/8/10.
     *
     * @author 王启航
     * @version 1.0
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface Interface {
        String value();
    }

    注意上面@Target(ElementType.METHOD)表示该方法仅仅能加在方法上面;@Retention(RetentionPolicy.CLASS)表示是在编译时存在的。

    好了接下来就是重点了:

    public class InterfaceProcessor extends AbstractProcessor {
    
        @Override
        public synchronized void init(ProcessingEnvironment env){ }
    
        @Override
        public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() { }
    
        @Override
        public SourceVersion getSupportedSourceVersion() { }
    
    }

    定义自己的注解处理器

    • init对一些工具进行初始化。

    • process就是真正生成java代码的地方。
    • getSupportedAnnotationTypes表示该注解处理器能够出来那些注解。
    • getSupportedSourceVersion能够出来java版本号

    我们首先看看除去process的其它三个方法:

    public class InterfaceProcessor extends AbstractProcessor {
    
        private Types typeUtils;
        private Elements elementUtils;
        private Filer filer;
        private Messager messager;
    
    
        @Override
        public synchronized void init(ProcessingEnvironment env) {
            super.init(env);
            elementUtils = env.getElementUtils();
            filer = env.getFiler();
            typeUtils = env.getTypeUtils();
            messager = env.getMessager();
        }
    
        @Override
        public boolean process(Set<?

    extends TypeElement> annotations, RoundEnvironment env) { } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new TreeSet<>(); types.add(Interface.class.getCanonicalName()); return types; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }

    getSupportedSourceVersion返回近期的版本号。
    getSupportedAnnotationTypes返回了我们定义的Interface注解。
    init初始化了了4个工具类。以下略微介绍一哈:

    Elements:Element代表程序的元素,比如包、类或者方法。每一个Element代表一个静态的、语言级别的构件。它仅仅是结构化的文本。他不是可执行的.能够理解为一个签名
    (public class Main 或者 private void fun(int a) {}),大家能够联系到XML的解析,或者抽象语法树 (AST)。
    给出以下样例:

    package com.example;    // PackageElement
    
    public class Foo {        // TypeElement
    
        private int a;      // VariableElement
        private Foo other;  // VariableElement
    
        public Foo () {}    // ExecuteableElement
    
        public void setA (  // ExecuteableElement
                         int newA   // TypeElement
                         ) {}
    }

    并且我们能够通过

    aType.getEnclosingElement();

    得到其父标签(没有父标签就返回null).能够联系Class.getEnclosingClass这种方法。

    上面样例大家都懂了吧!!

    本文主要就是对ExecuteableElement进行操作。

    以下看看Messager
    由于在注解处理器里面不能够抛出Exception!为什么了?由于在编译阶段抛出错误后,注解处理器就不会执行完。也就没什么用了。
    所以Message就是为了输出错误信息。

    关于FilterTypes本文就不进行解说了。。。。

    好!!以下看看process方法

    @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
            Map<String, AnnotatedClass> classMap = new HashMap<>();
    
            // 得到全部注解@Interface的Element集合
            Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);
    
            for (Element e : elementSet) {
                if (e.getKind() != ElementKind.METHOD) {
                    error(e, "错误的注解类型。仅仅有函数能够被该 @%s 注解处理", Interface.class.getSimpleName());
                    return true;
                }
    
                ExecutableElement element = (ExecutableElement) e;
                AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
    
                String classname = annotatedMethod.getSimpleClassName();
                AnnotatedClass annotatedClass = classMap.get(classname);
                if (annotatedClass == null) {
                    PackageElement pkg = elementUtils.getPackageOf(element);
                    annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
                    annotatedClass.addMethod(annotatedMethod);
                    classMap.put(classname, annotatedClass);
                } else
                    annotatedClass.addMethod(annotatedMethod);
    
            }
            // 代码生成
            for (AnnotatedClass annotatedClass : classMap.values()) {
                annotatedClass.generateCode(elementUtils, filer);
            }
            return false;
        }

    首先看看第一部分:

     Map<String, AnnotatedClass> classMap = new HashMap<>();

    这里就是存储整个project凝视过getSupportedAnnotationTypes返回的注解的Map
    在这里我们定义了例如以下的数据结构:

    classMap:
    key -> 一个类的类名
    value -> AnnotatedClass
    
    // 这个等下会说到,这里知道是这种关系就好
    AnnotatedClass 
    key -> 一个类的类名
    value -> 该类全部加上注解的方法。

    第二部分:

    // 得到全部注解@InterfaceElement集合
            Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);

    如凝视所说,得到全部注解@InterfaceElement集合。

    注意这里得到了是Element的集合,也就生成了一个树结构。

    第三部分:

    for (Element e : elementSet) {
                if (e.getKind() != ElementKind.METHOD) {
                    error(e, "错误的注解类型,仅仅有函数能够被该 @%s 注解处理", Interface.class.getSimpleName());
                    return true;
                }
    
                ExecutableElement element = (ExecutableElement) e;
                AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
    
                String classname = annotatedMethod.getSimpleClassName();
                AnnotatedClass annotatedClass = classMap.get(classname);
                if (annotatedClass == null) {
                    PackageElement pkg = elementUtils.getPackageOf(element);
                    annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
                    annotatedClass.addMethod(annotatedMethod);
                    classMap.put(classname, annotatedClass);
                } else
                    annotatedClass.addMethod(annotatedMethod);
    
            }

    首先全部加上@InterfaceElement进行变量,第一步推断注解是否加在Method之上,假设不是就输出错误信息。
    上文说到能够用Messager来输出:

    private void error(Element e, String msg, Object... args) {
            messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
        }

    然后

     ExecutableElement element = (ExecutableElement) e;
     AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);

    将其转型为ExecutableElement。由于在上面推断全部的Element都是加在方法上的。这里不会报错。
    然后构造AnnotatedMethod,由于面向对象,AnnotatedMethod表示一个加了注解的方法。


    在这里我们看看什么是AnnotatedMethod:

    package wqh.core;
    
    import javax.lang.model.element.ExecutableElement;
    import javax.lang.model.element.TypeElement;
    
    /**
     * Created on 2016/8/10.
     *
     * @author 王启航
     * @version 1.0
     */
    public class AnnotatedMethod {
        private ExecutableElement annotatedMethodElement;
        private String simpleMethodName;
        private String simpleClassName;
    
        public AnnotatedMethod(ExecutableElement annotatedMethodElement) {
            this.annotatedMethodElement = annotatedMethodElement;
            simpleMethodName = annotatedMethodElement.getSimpleName().toString();
            /*
             * getEnclosingElement() 能够理解为该标签的父标签.
             * {@see Class.getEnclosingClass}
             */
            TypeElement parent = (TypeElement) annotatedMethodElement.getEnclosingElement();
            /*
             * Return the fully qualified name of this class or interface.
             * {@code java.util.Set<E>} is "java.util.Set"
             * {@code java.util.Map.Entry}is "java.util.Map.Entry"
             */
            simpleClassName = parent.getQualifiedName().toString();
        }
    
        public ExecutableElement getAnnotatedMethodElement() {
            return annotatedMethodElement;
        }
    
        public String getSimpleMethodName() {
            return simpleMethodName;
        }
    
        public String getSimpleClassName() {
            return simpleClassName;
        }
    }
    

    还是比較简单的哈!!

    OK,回到 process

    String classname = annotatedMethod.getSimpleClassName();
    AnnotatedClass annotatedClass = classMap.get(classname);

    构造AnnotatedClass。上面也说到过AnnotatedClass,AnnotatedClass能够表示一个类。里面存储了很多的AnnotatedMethod
    以下看看其代码:

    package wqh.core;
    
    
    import com.squareup.javapoet.JavaFile;
    import com.squareup.javapoet.MethodSpec;
    import com.squareup.javapoet.TypeName;
    import com.squareup.javapoet.TypeSpec;
    
    import javax.annotation.processing.Filer;
    import javax.lang.model.element.Modifier;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    import java.io.IOException;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * Created on 2016/8/10.
     *
     * @author 王启航
     * @version 1.0
     */
    public class AnnotatedClass {
        private String className;
        private String packageName;
        private List<AnnotatedMethod> methods = new LinkedList<>();
    
        /**
         * @param packageName       将要生成的类的包名
         * @param generateClassName 将要生成的类的类名
         */
        public AnnotatedClass(String packageName, String generateClassName) {
            this.className = generateClassName;
            this.packageName = packageName;
        }
    
       // 生成Java代码部分。如今能够不看
        public void generateCode(Elements elementUtils, Filer filer) {
            TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC);
    
            for (AnnotatedMethod m : methods) {
                MethodSpec.Builder methodBuilder =
                        MethodSpec.methodBuilder(m.getSimpleMethodName())
                                .addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
                                .returns(TypeName.get(m.getAnnotatedMethodElement().getReturnType()));
    
                int i = 1;
                for (VariableElement e : m.getAnnotatedMethodElement().getParameters()) {
                    methodBuilder.addParameter(TypeName.get(e.asType()), "param" + String.valueOf(i));
                    ++i;
                }
                typeBuilder.addMethod(methodBuilder.build());
            }
            JavaFile javaFile = JavaFile.builder(packageName, typeBuilder.build()).build();
    
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void addMethod(AnnotatedMethod annotatedMethod) {
            methods.add(annotatedMethod);
        }
    }
    

    OK。在process里面:

     if (annotatedClass == null) {
                    PackageElement pkg = elementUtils.getPackageOf(element);
                    annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
                    annotatedClass.addMethod(annotatedMethod);
                    classMap.put(classname, annotatedClass);
                } else
                    annotatedClass.addMethod(annotatedMethod);

    就是将加了注解的方法,在同一个类的增加到annotatedClass里面去,然后构成classMap

    到这里。全部的结构都已生成。也就是:

    classMap:
    key -> 一个类的类名
    value -> AnnotatedClass
    
    // 这个等下会说到。这里知道是这种关系就好
    AnnotatedClass 
    key -> 一个类的类名
    value -> 该类全部加上注解的方法。

    下一步就是生成Java代码了。这里用了JavaWriter的改进版本号JavaPoet,能够自己去下这个包

    // 代码生成
            for (AnnotatedClass annotatedClass : classMap.values()) {
                annotatedClass.generateCode(elementUtils, filer);
            }
            return false;

    这部分不做具体解说。

    好了,全部的InterfaceProcessor解说完成。

    注冊打包

    接下来将其注冊到JVM中去:在Intelilj IDEA,有例如以下的文件夹结构:
    Dir

    javax.annotation.processing.Processor中定义了注解处理器:

    wqh.core.InterfaceProcessor

    这些都做了之后。就回去生成jar包!!

    !能够用Maven,可是我们这个项目加了依赖项,所以有点麻烦。
    这里直接用Intelilj IDEA打包!
    File->Project Structure能够配置这样

    将右側的文件夹加到左側就好。
    Jar

    Build一哈,就能够,能够生成jar包了。!

    这些步骤。我们也能够直接使用Google的AutoService,直接省去了这一步。

    測试

    在其它项目中依照以下配置:
    Config
    Config2

    package test;
    
    import wqh.core.Interface;
    
    /**
     * Created on 2016/8/10.
     *
     * @author 王启航
     * @version 1.0
     */
    public class Man {
    
        @Interface("ManInterface")
        public void eat() {
            System.out.println("Eat");
        }
    }
    

    执行就能够在generated文件夹看到生成的接口

    package test;
    
    public interface ManInterface {
      void eat();
    }
    

    那么怎么使用MainInterface呢?

    package test;
    
    /**
     * Created on 2016/8/10.
     *
     * @author 王启航
     * @version 1.0
     */
    public class Main {
    
        public static void main(String args[]) throws ClassNotFoundException {
            Class<?

    > c = Class.forName("test.ManInterface"); System.out.println(c.getName()); } }

    尽管这个功能没什么用啊!

    。。!

    參考

    Java二十周年 Java注解处理器
    Java注解处理器

    我的邮箱:1906362072@qq.com
    大二学生,欢迎联系。

  • 相关阅读:
    05391
    05390
    05389
    05388
    1006 Sign In and Sign Out (25分)
    1002 A+B for Polynomials (25分)
    1005 Spell It Right (20分)
    1003 Emergency (25分)
    1001 A+B Format (20分)
    HDU 2962 Trucking
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/8366785.html
Copyright © 2020-2023  润新知