自定义Java注解处理器

Java注解与自定义注解处理器 所以本文就注解与自定义的注解处理器来学习注解。 阅读详情

原文: https://blog.youkuaiyun.com/jsonChumpKlutz/article/details/82422841

该文章是继Java注解解析-基础+运行时注解(RUNTIME)之后,使用注解处理器处理CLASS注解的文章。通过完整的Demo例子介绍整个注解处理器的搭建流程以及注意事项,你将知道如何去搭建自己的注解处理器。前提是你知道如何去写自定义注解,不清楚的可以点击我上面的链接哦~

介绍

顾名思义注解处理器就是javac包中专门用来处理注解的工具。所有的注解处理器都必须继承抽象类AbstractProcessor然后重写它的几个方法。注解处理器是运行在它自己的JVM中。javac启动一个完整Java虚拟机来运行注解处理器,这意味着你可以使用任何你在其他java应用中使用的的东西。其中抽象方法process是必须要重写的,再该方法中注解处理器可以遍历所有的源文件,然后通过RoundEnvironment类获取我们需要处理的注解所标注的所有的元素,这里的元素可以代表包,类,接口,方法,属性等,具体的解释放在下一章节,因为这里面牵扯到的东西实在是太多了。再处理的过程中可以利用特定的工具类自动生成特定的.java文件或者.class文件,来帮助我们处理自定义注解。

下面开始搭建

1.创建

首先注解处理器需要javax包的支持,我们的Android环境下是访问不到javax包的,除非我们自己配置。

//app:build.gradle中加入依赖,一定要使用provided files来引入.
provided files('/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/jre/lib/rt.jar')

所以我们需要创建Java Library包来提供javax环境,另外注解处理器要被打包进jar包里面才能被系统识别,这就是选用ava Library的原因,目前注解注解框架均是如此。

image

创建好Module之后就可以写我们的自定义的注解处理器了。首先需要继承抽象类AbstractProcessor,然后重写process()方法。该方法是核心方法,该方法将遍历源代码,找出我们想要注解标注的元素。单单重写这一个方法是不够的, 在我们的开发中往往需要重写init(),getSupportedSourceVersion(),getSupportedAnnotationTypes()这几个方法就可以了。另外再Java7及其以后我们还可以使用注解@SupportedSourceVersion()和@SupportedAnnotationTypes() 去替代上面的方法,见于该注解有Java版本的限制,所以还是建议直接重写方法为好。

public class AnnotationTwoProcessor extends AbstractProcessor {

    private Messager messager; //用于打印日志
    private Elements elementUtils; //用于处理元素
    private Filer filer;  //用来创建java文件或者class文件

    //该方法再编译期间会被注入一个ProcessingEnvironment对象,该对象包含了很多有用的工具类。
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();
    }

    /**
     * 该方法将一轮一轮的遍历源代码
     * @param set  该方法需要处理的注解类型
     * @param roundEnvironment 关于一轮遍历中提供给我们调用的信息.
     * @return  改轮注解是否处理完成 true 下轮或者其他的注解处理器将不会接收到次类型的注解.用处不大.
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        return false;
    }

    /**
     * 返回我们Java的版本.
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    /**
     * 返回我们将要处理的注解
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<>();
        annotataions.add(MyAnnotion.class.getCanonicalName());
        return annotataions;
    }
}

2.注册

注册注解处理器的方法有两种:

第一种: 处理器必须被打包进一个jar包里面,这也是为什么要建立一个Java Module。该jar包需要有一个特定路径resources/META-INF/services的文件javax.annotation.processing.Processor,该文件的路径和名称都是特定的,然后将我们声明注解处理器的全路径写到该文件中,这样Java虚拟机会自动找该路径下中我们声明的处理器。

image

我们再创建文件夹的时候一定要确定其命名的准确性。创建文件的时候直接右键->new file,保证我们的文件的名字为javax.annotation.processing.Processor。 
image 
创建成功之后我们将自定义的注解处理器的全路径写到该文件里面。

image

这样问题来了,如我我们写了多个注解处理器该怎么声明呢?接着看。如果我们一个Module里面声明了多个注解处理器,再该文件中声明的时候每个注解处理器要换行声明,运行的顺序就按声明的顺序去执行。这里需要对注解处理器的运行顺序解释一下,再编译期间并不是一个注解处理器完全的处理结束再开始下一个的,而是在扫描一轮源代码 的时候注册的第一个处理器先执行一轮,然后注册的第二个处理器开始执行然后。。。三个。。四个。第二轮的时候还是注册的第一个处理器先执行,然后第二个。。。三个。。。这里面的深刻解释会在下一篇讲解,这篇只是使用。

cn.example.wang.processor.AnnotationProcessor
cn.example.wang.processorAnnotationTwoProcessor

第二种:当觉得第一种方法配置繁琐的时候就会有新的更简洁的方式出现。谷歌公司推出的使用注解去注册注解处理器。

  1. 添加依赖,可以去GitHub上面查找最新版本。

    implementation 'com.google.auto.service:auto-service:1.0-rc4'
  2. 我们就可以使用了,它的作用和我们手写的作用是一样的。不过注释的处理器的处理顺序跟你类创建的顺序是一致的,跟方法一中文件声明的顺序不一样。

    @AutoService(Processor.class)
    public class AnnotationTwoProcessor extends AbstractProcessor {
    }

总的来说方式1的注册方式目前仅在EventBus里面有用到,方式2还是推荐使用的,比如:Arouter,BufferKnife等流行框架都是采用的这种方式注册的,方便快捷。

3.APP引用注解处理器

再引用注解处理器的Module的时候坑其实挺多的。首先我们得确保处理器所在的jar包会添加到我们运行的项目中,注解处理器才会被得到执行,这里呢因为源码不清楚,所以只好自己去试了。Application引入注解处理器包的时候并不像我们引入其它的Android Module一样,这里列举三种种引入方法。

  • plugin: 'com.android.application'

    我们可以使用implementation,compile,api直接引用注解处理器的module,但是会有一个编译错误:

    Error:Execution failed for task ':app:javaPreCompileDebug'.
    > Annotation processors must be explicitly declared now.  The following dependencies on the compile classpath are found to contain annotation processor.  Please add them to the annotationProcessor configuration.
      - annotation_processor.jar (project :annotation_processor)
    Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior.  Note that this option is deprecated and will be removed in the future.
    See https://developer.android.com/r/tools/annotation-processor-error-message.html for more details.

    我们需要加一段代码,代码位置如下所示,这样就可以愉快的引入注解处理器了:

    android {
      defaultConfig {
          javaCompileOptions {
              annotationProcessorOptions {
                  includeCompileClasspath = true
              }
          }
      }
    }

    另外我们可以用annotationProcessor引入注解处理器,这个引入方式是为了替换Gradle版本2.2之前的apt引入方法。该引入方式专门用来处理注解处理器。使用该引入方式的时候不会出现错误提示,也是Android中推荐使用的引入方法,该方式也是主流方式。

    annotationProcessor project(':annotation_processor')
  • apply plugin: 'com.android.library'

    前提module一定要被android application引入。module里面引入注解处理器的module的话,基本跟android application中一致,这里说一下两个不同点annotationProcessor和implementation方式的引入都不会执行注解处理器,真实的原理并不清楚,只能猜测是application才能正真的处理注解,所以得把依赖暴露出去。这点再android library的module中一定得注意。不太建议该方式引入。

  • apply plugin: 'java-library'

    前提java library一定要被android application引入。声明这样的module 的话我们就可以直接引入注解处理器了,也不用担心用什么方式引入。不过这种场景不太多。

如何确保你的注解处理器已经注册成功了。首先你已经自定义好了一个注解,并且该注解已经用到了你的代码里面。如下代码所示,你已经设置了我们要处理的是那种类型的注解(代码1所示),然后再我们的process方法里面打上日志(代码2所示),然后点击Android Studio的Make Project按钮,之后打开Gradle Console窗口看build信息,当你发现信息中打印了代码2所示的信息之后就表明你的注解处理器已经运行起来了。如果没有打印信息的话尝试 clean一下项目然后重新Make Project。如果发现没有打印日志的话,尝试查看注解处理器是否已经注册和注解处理器所在的module是否被android application成功引入。

@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {

    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //代码2
        messager.printMessage(Diagnostic.Kind.NOTE,"日志开始---------------");
        return false;
    }


    //代码1
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedOptions = new HashSet<>();
        supportedOptions.add(MyAnnotion.class.getCanonicalName());
        return supportedOptions;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }
}

4.自动生成Java文件

走到该步骤的时候,你要确保你的注解处理器已经正常的运行。我们使用注解处理器处理源码注解的目的,就是再编译期间完成我们对某个注解的处理工作。比如:BufferKnife再编译期间会在使用特定注解的文件路径生成*—ViewBinding的源文件去处理特定注解。这里用一个Demo去演示如何生成代码:

先看我的注解:

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.LOCAL_VARIABLE})
public @interface MyAnnotion {
    String value() default "ssssss";

}

在我的MainActivity上面使用注解:

@MyAnnotion()
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

接着使用注解处理器去处理注解,生成对应的MainActivity_ViewBinding源文件。

public class AnnotationProcessor extends AbstractProcessor {

    private Messager messager;
    private Elements elementUtils;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE,"日志开始---------------");

        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MyAnnotion.class);
        for (Element element:elementsAnnotatedWith) {
            if(element.getKind() == ElementKind.CLASS){
                TypeElement typeElement = (TypeElement) element;
                PackageElement packageElement = elementUtils.getPackageOf(element);
                String packagePath = packageElement.getQualifiedName().toString();
                String className = typeElement.getSimpleName().toString();
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packagePath + "." + className + "_ViewBinding", typeElement);
                    Writer writer = sourceFile.openWriter();
                    writer.write("package  "+packagePath +";\n");
                    writer.write("import  "+packagePath+"."+className+";\n");
                    writer.write("public class "+className+"_ViewBinding"+"  { \n");
                    writer.write("\n");
                    writer.append("       public "+className +"  targe;\n");
                    writer.write("\n");
                    writer.append("}");
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


        messager.printMessage(Diagnostic.Kind.NOTE,"日志结束---------------");
        return false;
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedOptions = new HashSet<>();
        supportedOptions.add(MyAnnotion.class.getCanonicalName());
        return supportedOptions;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }
}

结果展示:

注意一下生成的位置!我们可以直接再我们正常的代码中应用到该文件,因为该文件是会生成class文件的。

5.总结

该文章只是介绍了如何搭建起一个Java注解处理器,没有更深入的去讲解AbstractProcessor类以及我们再处理注解的过程中用到的各种类的API。当然接下来的文章就会详细的介绍注解处理器所使用到的类,方法,属性等的用法和意义,这一定是史上最全的注解处理器API。之后你会更加随心所欲的去构建自己的注解框架。

第二篇:

基本实现

实现一个自定义注解处理器需要有两个步骤,第一是实现Processor接口处理注解,第二是注册注解处理器。

实现Processor接口

通过实现Processor接口可以自定义注解处理器,这里我们采用更简单的方法通过继承AbstractProcessor类实现自定义注解处理器。实现抽象方法process处理我们想要的功能。

public class CustomProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        return false;
    }
}

除此之外,我们还需要指定支持的注解类型以及支持的Java版本通过重写getSupportedAnnotationTypes方法和getSupportedSourceVersion方法:

public class CustomProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        return false;
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add(CustomAnnotation.class.getCanonicalName());
        return annotataions;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

对于指定支持的注解类型,我们还可以通过注解的方式进行指定:

@SupportedAnnotationTypes({"io.github.yuweiguocn.annotation.CustomAnnotation"})
public class CustomProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        return false;
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

因为Android平台可能会有兼容问题,建议使用重写getSupportedAnnotationTypes方法指定支持的注解类型。

注册注解处理器

最后我们还需要将我们自定义的注解处理器进行注册。新建res文件夹,目录下新建META-INF文件夹,目录下新建services文件夹,目录下新建javax.annotation.processing.Processor文件,然后将我们自定义注解处理器的全类名写到此文件:

io.github.yuweiguocn.processor.CustomProcessor

上面这种注册的方式太麻烦了,谷歌帮我们写了一个注解处理器来生成这个文件。
github地址:https://github.com/google/auto
添加依赖:

compile 'com.google.auto.service:auto-service:1.0-rc2'

添加注解:

@AutoService(Processor.class)
public class CustomProcessor extends AbstractProcessor {
    ...
}

搞定,体会到注解处理器的强大木有。后面我们只需关注注解处理器中的处理逻辑即可。

我们来看一下最终的项目结构:

基本概念

抽象类中还有一个init方法,这是Processor接口中提供的一个方法,当我们编译程序时注解处理器工具会调用此方法并且提供实现ProcessingEnvironment接口的对象作为参数。

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
}

我们可以使用ProcessingEnvironment获取一些实用类以及获取选项参数等:

方法说明
Elements getElementUtils()返回实现Elements接口的对象,用于操作元素的工具类。
Filer getFiler()返回实现Filer接口的对象,用于创建文件、类和辅助文件。
Messager getMessager()返回实现Messager接口的对象,用于报告错误信息、警告提醒。
Map<String,String> getOptions()返回指定的参数选项。
Types getTypeUtils()返回实现Types接口的对象,用于操作类型的工具类。

元素

Element元素是一个接口,表示一个程序元素,比如包、类或者方法。以下元素类型接口全部继承自Element接口:

类型说明
ExecutableElement表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素。
PackageElement表示一个包程序元素。提供对有关包及其成员的信息的访问。
TypeElement表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
TypeParameterElement表示一般类、接口、方法或构造方法元素的形式类型参数。
VariableElement表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

如果我们要判断一个元素的类型,应该使用Element.getKind()方法配合ElementKind枚举类进行判断。尽量避免使用instanceof进行判断,因为比如TypeElement既表示类又表示一个接口,这样判断的结果可能不是你想要的。例如我们判断一个元素是不是一个类:

if (element instanceof TypeElement) { //错误,也有可能是一个接口
}

if (element.getKind() == ElementKind.CLASS) { //正确
    //doSomething
}

下表为ElementKind枚举类中的部分常量,详细信息请查看官方文档。

类型说明
PACKAGE一个包。
ENUM一个枚举类型。
CLASS没有用更特殊的种类(如 ENUM)描述的类。
ANNOTATION_TYPE一个注解类型。
INTERFACE没有用更特殊的种类(如 ANNOTATION_TYPE)描述的接口。
ENUM_CONSTANT一个枚举常量。
FIELD没有用更特殊的种类(如 ENUM_CONSTANT)描述的字段。
PARAMETER方法或构造方法的参数。
LOCAL_VARIABLE局部变量。
METHOD一个方法。
CONSTRUCTOR一个构造方法。
TYPE_PARAMETER一个类型参数。

类型

TypeMirror是一个接口,表示 Java 编程语言中的类型。这些类型包括基本类型、声明类型(类和接口类型)、数组类型、类型变量和 null 类型。还可以表示通配符类型参数、executable 的签名和返回类型,以及对应于包和关键字 void 的伪类型。以下类型接口全部继承自TypeMirror接口:

类型说明
ArrayType表示一个数组类型。多维数组类型被表示为组件类型也是数组类型的数组类型。
DeclaredType表示某一声明类型,是一个类 (class) 类型或接口 (interface) 类型。这包括参数化的类型(比如 java.util.Set<String>)和原始类型。TypeElement 表示一个类或接口元素,而 DeclaredType 表示一个类或接口类型,后者将成为前者的一种使用(或调用)。
ErrorType表示无法正常建模的类或接口类型。
ExecutableType表示 executable 的类型。executable 是一个方法、构造方法或初始化程序。
NoType在实际类型不适合的地方使用的伪类型。
NullType表示 null 类型。
PrimitiveType表示一个基本类型。这些类型包括 boolean、byte、short、int、long、char、float 和 double。
ReferenceType表示一个引用类型。这些类型包括类和接口类型、数组类型、类型变量和 null 类型。
TypeVariable表示一个类型变量。
WildcardType表示通配符类型参数。

同样,如果我们想判断一个TypeMirror的类型,应该使用TypeMirror.getKind()方法配合TypeKind枚举类进行判断。尽量避免使用instanceof进行判断,因为比如DeclaredType既表示类 (class) 类型又表示接口 (interface) 类型,这样判断的结果可能不是你想要的。

TypeKind枚举类中的部分常量,详细信息请查看官方文档。

类型说明
BOOLEAN基本类型 boolean。
INT基本类型 int。
LONG基本类型 long。
FLOAT基本类型 float。
DOUBLE基本类型 double。
VOID对应于关键字 void 的伪类型。
NULLnull 类型。
ARRAY数组类型。
PACKAGE对应于包元素的伪类型。
EXECUTABLE方法、构造方法或初始化程序。

创建文件

Filer接口支持通过注解处理器创建新文件。可以创建三种文件类型:源文件、类文件和辅助资源文件。

1.创建源文件

JavaFileObject createSourceFile(CharSequence name,
                                Element... originatingElements)
                                throws IOException

创建一个新的源文件,并返回一个对象以允许写入它。文件的名称和路径(相对于源文件的根目录输出位置)基于该文件中声明的类型。如果声明的类型不止一个,则应该使用主要顶层类型的名称(例如,声明为 public 的那个)。还可以创建源文件来保存有关某个包的信息,包括包注解。要为指定包创建源文件,可以用 name 作为包名称,后跟 ".package-info";要为未指定的包创建源文件,可以使用 "package-info"。

2.创建类文件

JavaFileObject createClassFile(CharSequence name,
                               Element... originatingElements)
                               throws IOException

创建一个新的类文件,并返回一个对象以允许写入它。文件的名称和路径(相对于类文件的根目录输出位置)基于将写入的类型名称。还可以创建类文件来保存有关某个包的信息,包括包注解。要为指定包创建类文件,可以用 name 作为包名称,后跟 ".package-info";为未指定的包创建类文件不受支持。

3.创建辅助资源文件

FileObject createResource(JavaFileManager.Location location,
                          CharSequence pkg,
                          CharSequence relativeName,
                          Element... originatingElements)
                          throws IOException

创建一个用于写入操作的新辅助资源文件,并为它返回一个文件对象。该文件可以与新创建的源文件、新创建的二进制文件或者其他受支持的位置一起被查找。位置 CLASS_OUTPUT 和 SOURCE_OUTPUT 必须受支持。资源可以是相对于某个包(该包是源文件和类文件)指定的,并通过相对路径名从中取出。从不太严格的角度说,新文件的完全路径名将是 location、 pkg 和 relativeName 的串联。

对于生成Java文件,还可以使用Square公司的开源类库JavaPoet,感兴趣的同学可以了解下。

打印错误信息

Messager接口提供注解处理器用来报告错误消息、警告和其他通知的方式。

注意:我们应该对在处理过程中可能发生的异常进行捕获,通过Messager接口提供的方法通知用户。此外,使用带有Element参数的方法连接到出错的元素,用户可以直接点击错误信息跳到出错源文件的相应行。如果你在process()中抛出一个异常,那么运行注解处理器的JVM将会崩溃(就像其他Java应用一样),这样用户会从javac中得到一个非常难懂出错信息。

方法说明
void printMessage(Diagnostic.Kind kind, CharSequence msg)打印指定种类的消息。
void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e)在元素的位置上打印指定种类的消息。
void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a)在已注解元素的注解镜像位置上打印指定种类的消息。
void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v)在已注解元素的注解镜像内部注解值的位置上打印指定种类的消息。

配置选项参数

我们可以通过getOptions()方法获取选项参数,在gradle文件中配置选项参数值。例如我们配置了一个名为yuweiguoCustomAnnotation的参数值。

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ yuweiguoCustomAnnotation : 'io.github.yuweiguocn.customannotation.MyCustomAnnotation' ]
            }
        }
    }
}

在注解处理器中重写getSupportedOptions方法指定支持的选项参数名称。通过getOptions方法获取选项参数值。

public static final String CUSTOM_ANNOTATION = "yuweiguoCustomAnnotation";

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   try {
       String resultPath = processingEnv.getOptions().get(CUSTOM_ANNOTATION);
       if (resultPath == null) {
           ...
           return false;
       }
       ...
   } catch (Exception e) {
       e.printStackTrace();
       ...
   }
   return true;
}


@Override
public Set<String> getSupportedOptions() {
   Set<String> options = new LinkedHashSet<String>();
   options.add(CUSTOM_ANNOTATION);
   return options;
}

处理过程

Java官方文档给出的注解处理过程的定义:注解处理过程是一个有序的循环过程。在每次循环中,一个处理器可能被要求去处理那些在上一次循环中产生的源文件和类文件中的注解。第一次循环的输入是运行此工具的初始输入。这些初始输入,可以看成是虚拟的第0次的循环的输出。这也就是说我们实现的process方法有可能会被调用多次,因为我们生成的文件也有可能会包含相应的注解。例如,我们的源文件为SourceActivity.class,生成的文件为Generated.class,这样就会有三次循环,第一次输入为SourceActivity.class,输出为Generated.class;第二次输入为Generated.class,输出并没有产生新文件;第三次输入为空,输出为空。

每次循环都会调用process方法,process方法提供了两个参数,第一个是我们请求处理注解类型的集合(也就是我们通过重写getSupportedAnnotationTypes方法所指定的注解类型),第二个是有关当前和上一次 循环的信息的环境。返回值表示这些注解是否由此 Processor 声明,如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们。

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

获取注解元素

我们可以通过RoundEnvironment接口获取注解元素。process方法会提供一个实现RoundEnvironment接口的对象。

方法说明
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a)返回被指定注解类型注解的元素集合。
Set<? extends Element> getElementsAnnotatedWith(TypeElement a)返回被指定注解类型注解的元素集合。
processingOver()如果循环处理完成返回true,否则返回false。

示例

了解完了相关的基本概念,接下来我们来看一个示例,本示例只为演示无实际意义。主要功能为自定义一个注解,此注解只能用在public的方法上,我们通过注解处理器拿到类名和方法名存储到List集合中,然后生成通过参数选项指定的文件,通过此文件可以获取List集合。

自定义注解:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
}

注解处理器中关键代码:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   try {
       String resultPath = processingEnv.getOptions().get(CUSTOM_ANNOTATION);
       if (resultPath == null) {
           messager.printMessage(Diagnostic.Kind.ERROR, "No option " + CUSTOM_ANNOTATION +
                   " passed to annotation processor");
           return false;
       }

       round++;
       messager.printMessage(Diagnostic.Kind.NOTE, "round " + round + " process over " + roundEnv.processingOver());
       Iterator<? extends TypeElement> iterator = annotations.iterator();
       while (iterator.hasNext()) {
           messager.printMessage(Diagnostic.Kind.NOTE, "name is " + iterator.next().getSimpleName().toString());
       }

       if (roundEnv.processingOver()) {
           if (!annotations.isEmpty()) {
               messager.printMessage(Diagnostic.Kind.ERROR,
                       "Unexpected processing state: annotations still available after processing over");
               return false;
           }
       }

       if (annotations.isEmpty()) {
           return false;
       }

       for (Element element : roundEnv.getElementsAnnotatedWith(CustomAnnotation.class)) {
           if (element.getKind() != ElementKind.METHOD) {
               messager.printMessage(
                       Diagnostic.Kind.ERROR,
                       String.format("Only methods can be annotated with @%s", CustomAnnotation.class.getSimpleName()),
                       element);
               return true; // 退出处理
           }

           if (!element.getModifiers().contains(Modifier.PUBLIC)) {
               messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element);
               return true;
           }

           ExecutableElement execElement = (ExecutableElement) element;
           TypeElement classElement = (TypeElement) execElement.getEnclosingElement();
           result.add(classElement.getSimpleName().toString() + "#" + execElement.getSimpleName().toString());
       }
       if (!result.isEmpty()) {
           generateFile(resultPath);
       } else {
           messager.printMessage(Diagnostic.Kind.WARNING, "No @CustomAnnotation annotations found");
       }
       result.clear();
   } catch (Exception e) {
       e.printStackTrace();
       messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in CustomProcessor: " + e);
   }
   return true;
}

private void generateFile(String path) {
   BufferedWriter writer = null;
   try {
       JavaFileObject sourceFile = filer.createSourceFile(path);
       int period = path.lastIndexOf('.');
       String myPackage = period > 0 ? path.substring(0, period) : null;
       String clazz = path.substring(period + 1);
       writer = new BufferedWriter(sourceFile.openWriter());
       if (myPackage != null) {
           writer.write("package " + myPackage + ";\n\n");
       }
       writer.write("import java.util.ArrayList;\n");
       writer.write("import java.util.List;\n\n");
       writer.write("/** This class is generated by CustomProcessor, do not edit. */\n");
       writer.write("public class " + clazz + " {\n");
       writer.write("    private static final List<String> ANNOTATIONS;\n\n");
       writer.write("    static {\n");
       writer.write("        ANNOTATIONS = new ArrayList<>();\n\n");
       writeMethodLines(writer);
       writer.write("    }\n\n");
       writer.write("    public static List<String> getAnnotations() {\n");
       writer.write("        return ANNOTATIONS;\n");
       writer.write("    }\n\n");
       writer.write("}\n");
   } catch (IOException e) {
       throw new RuntimeException("Could not write source for " + path, e);
   } finally {
       if (writer != null) {
           try {
               writer.close();
           } catch (IOException e) {
               //Silent
           }
       }
   }
}

private void writeMethodLines(BufferedWriter writer) throws IOException {
   for (int i = 0; i < result.size(); i++) {
       writer.write("        ANNOTATIONS.add(\"" + result.get(i) + "\");\n");
   }
}

编译输出:

Note: round 1 process over false
Note: name is CustomAnnotation
Note: round 2 process over false
Note: round 3 process over true

获取完整代码:https://github.com/yuweiguocn/CustomAnnotation

关于上传自定义注解处理器到jcenter中,请查看上传类库到jcenter。

很高兴你能阅读到这里,此时再去看EventBus 3.0中的注解处理器的源码,相信你可以很轻松地理解它的原理。

深入了解torch框架 大国的游戏,政府支持到位,是未来;AI : Artificial Intelligence,旨在研究、开发能够模拟、延伸和扩展人类智能的理论、方法、技术及应用系统,是一种拥有自主学习和推理能力的技术。它模仿人类大脑某些功能,包括感知、学习、理解、决策和问题解决。AI本质​ NLP(说话,听)、CV(眼睛)、自动驾驶、机器人(肢体动作)、大模型三要素:数据、网络、算力① 神经网络:找到合适的数学公式;② 训练:用已有数据训练网络,目标是求最优解;③ 推理:用模型预测新样本;PyTorch,简称Torch 阅读详情

相关推荐

5分钟快速验证lvgl界面:VScode模拟器极简配置法(免环境变量版)

本文介绍了一种5分钟快速验证LVGL界面的极简配置方法,使用VSCode模拟器免环境变量配置,适合UI设计师和产品经理快速预览界面效果。通过预编译的SDL2库和配置好的VSCode工程模板,实现开箱即用的LVGL界面验证,大幅提升工作效率。

weixin_30790841的博客 314

使用Java的注解实现自定义注解和注解处理器

在实际应用中,可以根据需求扩展和定制注解和注解处理器的功能,并根据具体的业务逻辑进行相应的处理操作。请注意,注解处理器的使用和配置可能会因编译器和开发环境而有所不同,请根据具体情况进行相应的调整和配置。使用Java的注解可以实现自定义注解和注解处理器,注解处理器可以在编译时或运行时处理注解,并根据注解信息进行相应的逻辑处理。- 在 `process()` 方法中获取被注解标记的元素,并根据注解信息进行相应的处理逻辑。- 可以使用元注解对注解进行更多的配置,如指定注解的生命周期、可重复性等。

m0_75198698的博客 847

OpenCV函数用法之calibrateCamera

参考链接:opencv中标定函数calibrateCamera_ychl87的专栏-优快云博客 世界坐标系的三维点投影到成像坐标系中的二维点的投影公式如下: 其中(X,Y,Z)为世界坐标系中的三维点; (u,v)为成像面坐标系中的二维点; A为相机的内参数矩阵:(cx,cy)为主光轴点,一般为图像的中心;fx和fy为焦距; [R|t]为相机的外参数矩阵:R为旋转矩阵,t为位移矩阵; 上述公式的简单推理过程如下 考虑到镜头畸变 其中,k1,k2,k3,k4,k5和k6为径向畸变,p1和.

tiger&sheep的博客 3万+

Java注解处理器:自定义注解的自动化处理

首先,定义一个自定义注解,指定其用于什么元素上,以及是否保留。Retention;Target;现在,你可以在你的代码中使用自定义注解了。// 类定义。

linzhongshu的专栏 962

Aveva Marine C# 二次开发入门001

1# 引用 C:\AVEVA\Marine\OH12.1.SP4\Aveva.ApplicationFramework.dll C:\AVEVA\Marine\OH12.1.SP4\Aveva.ApplicationFramework.Presentation.dll 2# 引用命名空间, using Aveva.ApplicationFramework.Presentation;usi...

weixin_30245867的博客 1911

VScode利用EIDE和cortex-debug进行stm32开发(也适用51)

你是否因为keil页面太丑而烦恼,你是否因为keil编译太慢而恼怒,ok阿,今天也是给大家带来怎么在vscode上进行stm32的编程(我不会告诉你,我搞了好几天才搞好的💔💔💔)为什么是简单介绍?因为太啰嗦了,我嫌麻烦😋,大家可以去EIDE的官方文档去看。

weixin_73952614的博客 9391

Java注解(三):自定义Java编译时注解处理器

1. 絮絮叨叨 要么是注解跟我有仇,要么是公司配发的笔记本跟我有仇,要么是因为心急吃不了热豆腐 痛定思痛:从头开始,新建一个Java项目,实现一个超级简单的注解@Hello 在 process()方法中简单打印 getSupportedSourceVersion()方法返回的结果 在此,记录一下如何定义一个简单的Java编译时注解处理器 1.1 AbstractProcessor 自定义Java编译时注解处理器,一般都是通过继承AbstractProcessor实现的 AbstractProcesso

lucyLee的博客 9060

别再写爬虫了!用Trae平台5分钟搞定一个能聊天的网页数据抓取Agent

本文介绍了如何利用Trae平台快速构建无需编程的网页数据抓取智能体,实现对话式数据采集。通过自然语言指令,用户可在5分钟内完成从数据抓取到分析的全流程,显著降低技术门槛和维护成本。文章详细解析了Trae智能体的核心能力,包括自然语言理解、智能页面解析、数据清洗和可视化配置,并提供了多个实战案例,展示其在竞品监控和舆情分析中的高效应用。

weixin_29038303的博客 717

java 获取方法体_java – 如何从ExecutableElement获取方法体

在我的AbstractProcessor中,我能够从一个带有一些注释的类中获取所有方法,我创建了:List extends Element> allElements = processingEnv.getElementUtils().getAllMembers((TypeElement) bean);List methods = ElementFilter.methodsIn(allElem...

weixin_32002829的博客 576

基于 YOLO26 的车辆实时检测与计数系统

本文提出了一种基于YOLO26的车辆实时检测与计数系统。系统采用CSPDarknet53作为主干网络,结合特征金字塔和多尺度预测实现高精度车辆检测。通过DeepSORT算法进行目标跟踪,并统计车辆数量、行驶方向和交通流量。实验表明,该系统在保持实时性能的同时,检测精度达到92.3%,可有效应用于智能交通管理。系统采用Python实现,支持GPU加速,适用于多种交通场景下的车辆监测需求。

一键难忘的博客 907

1小时读完100篇文献的正确打开方式

各位同仁好,我是七哥。一个在高校里从事人工智能 相关领域研究,钻研用大模型AI实操的学术人。可以和七哥交流学术写作或Gemini、GPT、Claude 等大模型 学术实操相关问题,多多交流,相互成就,共同进步。文献阅读与整理一般都是最先开始、却也最容易被低估的一步。很多学术同仁一开始就陷入越读越多、越读越乱的状态:论文下载了一大堆,但真正能用来支撑自己观点的内容却始终难以提炼出来。一方面,我们需要通过文献了解该研究领域的整体发展脉络,把握当前学界已经讨论到什么程度;

智能AI让学术科研更容易 49

自定义注解之编译时注解(RetentionPolicy.CLASS)(三)—— 常用接口介绍

前面介绍了关于编译时注解的使用方式,这里再补充一个关于注解处理器开发中的一些常用类、接口的使用方式和概念。 Element和TypeMirror 我觉得这两个是开发注解处理器最重要的两个概念,理解这两个的概念和使用是非常有必要的。 这两个都是接口,先来看一下Element的定义: /** * 表示一个程序元素,比如包、类或者方法,有如下几种子接口: * ExecutableEleme...

你的才华撑不起野心时,静下心学习;你的能力驾驭不了目标时,沉下心努力 596

【华为OD机试真题E卷】474、推荐多样性 | 机试真题+思路参考+代码解析(E卷复用)(C++、Java、Py)

题目描述 >推荐多样性需要从多个列表中选择元素,一次性要返回 N 屏数据(窗口数量),每屏展示 K 个元素(窗口大小),选择策略:   各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列表中为每屏选择一个元素,依次类推   每个列表的元素尽量均分为 N 份,如果不够 N 个,也要全部分配完,参考样例图:

KJ.JK 4434

java自定义注解处理器_自定义Java里的注解处理器(完)

注解处理过程如图所示,Java编译过程可分为三个步骤:1.将源文件解析为抽象语法树;2.调用已注册的注解处理器;3.生成字节码。如果在第 2 步调用注解处理器过程中生成了新的源文件,那么编译器将重复第 1、2 步,解析并且处理新生成的源文件。每次重复我们称之为一轮(Round)。注解处理是按照 Round 的顺序进行的。在每次进行 Round 操作时,都会请求 Processor 处理在前一个处理...

weixin_39861627的博客 749

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

java自定义注解处理器_java自定义注解学习(注解处理器)

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。注解处理器类库(java.lang.reflect.AnnotatedElement):Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口...

weixin_39818631的博客 253

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

java自定义注解处理器_深入理解Java:注解(Annotation)--注解处理器

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。注解处理器类库(java.lang.reflect.AnnotatedElement):Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口...

weixin_35161750的博客 99

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

利用Java的注解处理器生成代码:自定义注解处理器的开发

3. 配置注解处理器:在项目的`META-INF/services`目录下创建一个名为`javax.annotation.processing.Processor`的文件,文件内容为自定义注解处理器的完整类名(包括包路径)。5. 生成代码:在注解处理器的`process`方法中,可以通过`javax.lang.model.util.Elements`和`javax.lang.model.util.Filer`等工具类,生成代码文件。注解可以包含属性,用于传递额外的信息给注解处理器。

m0_75198698的博客 857

java自定义注解处理器_Android自定义注解处理器

前言在android开发中有很多运用到注解处理器(annotation processing)的框架,如常见的Butterknife,Dagger2,EventBus等,运用这些注解处理器框架大大简化了我们的代码。这里主要讲解自定义注解处理器的原理。知识点在自定义注解时,一般需要掌握以下几个知识点:AbstractProcessor相关知识auto-service相关知识javapoet相关知识A...

weixin_30994645的博客 332

java自定义注解处理器_Java注解annotation用法和自定义注解处理器

前言:在J2EE中,注解使得开发更加便利,省去了生成XML文件的过程,在Hibernate实体声明中,可以简简单单的用几个注解就可以免去生成一个XML的文件操作。这里就主要论述一下annotation的用法和自定义注解处理器。当在创建描述符性质的类或接口时,有大量重复性的工作时候,就可以利用注解来实现。基本语法:Java目前包括三种标准注解和四种元注解。元注解主要负责注解其他注解的。三种标准注解:...

weixin_29503445的博客 146

Gradle中如何自定义实现Java注解处理器 —— 源代码

Gradle中如何自定义实现Java注解处理器 —— 源代码 去发现同类优质开源项目:https://gitcode.com/ 此仓库包含了博客文章《Gradle中如何自定义实现Java注解处理器》中实现的源代码。此代码演示了如何在Gradle项目中自定义Java注解处理器,进而帮助开发者理解注解处理器的原理和应用。 在源代码中,您将看到如何设置Gradle构建脚本以编译注解处理器,并将其应用于项...

gitblog_06765的博客 305

Gradle中自定义Java注解处理器源代码:深入解析与应用指南

Gradle中自定义Java注解处理器源代码:深入解析与应用指南 去发现同类优质开源项目:https://gitcode.com/ 项目核心功能/场景 自定义Java注解处理器,简化代码开发流程。 项目介绍 在现代Java开发中,注解处理器已经成为一种重要的代码生成和自动化工具。本项目提供了一份详细的源代码,展示了如何在Gradle中自定义实现Java注解处理器。通过学习此项目,开发者可以理解注解...

gitblog_06754的博客 970

解析 Java 的注解处理器:自定义注解与代码生成

注解的元注解配置Target:指定注解适用的程序元素(如METHODRetention:定义注解保留阶段(用于编译期处理,SOURCE仅保留在源码中)。Repeatable:允许重复注解(Java 8+ 支持)。注解属性的设计模式基础类型:使用StringClass等简单类型作为属性。枚举类型:通过避免枚举类参与编译。嵌套注解:支持注解中包含其他注解,实现复合配置。约束性设计通过注解生成 API 文档。使用@Inherited控制注解的继承性。反模式。

潜意识Java的博客 1274

java 自定义注解处理器_[转]Java中实现自定义的注解处理器

在之前的《简单实现ButterKnife的注解功能》中,使用了运行时的注解实现了通过编写注解绑定View与xml。由于运行时注解需要在Activity初始化中进行绑定操作,调用了大量反射相关代码,在界面复杂的情况下,使用这种方法就会严重影响Activity初始化效率。而ButterKnife使用了更高效的方式——Annotation Processor来完成这一工作。Annotation Proc...

weixin_42524004的博客 445

Java中的注解处理器:自定义注解与APT工具的应用场景

在Java开发中,注解(Annotation)是一种非常强大的工具,它不仅可以简化代码,还可以用于编译时的代码检查、代码生成等高级功能。今天我们就来深入探讨一下Java中的注解处理器(Annotation Processor),以及如何利用APT(Annotation Processing Tool)工具来实现自定义注解的应用场景。在Java中,APT(Annotation Processing Tool)是一种用来处理注解的工具,通过它可以在编译期间扫描和处理我们定义的注解。关键字定义一个注解。

微赚开发者技术分享博客 957

JSR-269 Java中的注解处理器:APT工具的原理应用与自定义注解

如果我们要自定义注解处理器,那么必须继承AbstractProcessor类,因此,理解这个类的方法至关重要。// 注解处理工具的集合,包含了众多工具类:如Filer用来编写新文件,Messager用来打印错误,Elements用来处理Element/***//*** 配置支持的选项,如果不重写这个方法,AbstractProcessor类中默认的实现会读取@SupportedOption注解的配置*/else/**

weixin_51360020的博客 1210

Java 注解及自定义注解处理器

注解介绍 注解,也被称为元数据(所谓的元数据,就是描述数据的数据)。 所以注解的主要作用就是给指定代码一些描述信息。这些指定代码可以是一个类、一个方法或者是一个属性。 Java注解是在Java SE5中被引入进来的,在Java中内置了三种注解以及四种元注解。 内置注解 内置注解 说明 @Override 表示当前的方法定义将覆盖超类中的方法,如果方法名或者...

zhangke 5219
上一篇: 浏览器层面缓存 Etag & If-None-Match等详解
下一篇: Javascript异常处理机制详解
shadow_zed
博客等级 码龄10年 6289粉丝 68原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值