Java注解之如何利用RetentionPolicySOURCE生存周期

33次阅读

共计 6552 个字符,预计需要花费 17 分钟才能阅读完成。

上一篇文章简单讲了下 Java 注解的学习之元注解说明,学习了 Java 注解是如何定义的,怎么使用的,但是并没有介绍 Java 的注解是怎么起作用的,像 Spring Boot 里面的那些注解,到底是怎么让程序这样子运行起来的?特别是讲到 RetentionPolicy 这一块,到底在 SOURCE 阶段,在 CLASS 阶段,在 RUNTIME 阶段有什么差别,注解是如何在这三个阶段起作用的呢?而且,在 SOURCE 阶段,在 CLASS 阶段,程序又不运行,那注解到底会用来做些什么呢?

带着这些疑问,我就去了解下了如何让注解起作用,发现 RUNTIME 阶段的介绍到处都是,但是 SOURCE 和 CLASS 阶段就很少文章讲解到了,真的得搜刮好几十篇文章才能成功的把程序跑起来,几乎每一篇文章都少讲了些东西。

本文优先讲的是 SOURCE 阶段注解如何发挥作用,发现这一块涉及的知识非常多且难,资料还少,另外还发现,Java 服务器端编程的人用这个反而不如 Android 开发的人用得多。对我学习 SOURCE 阶段的注解帮助最大的是 Pluggable Annotation Processing API,JSR269 插件化注解 API 以及 JVM 进阶 — 浅谈注解处理器。搜索这方面的资料用“插件化注解处理 API”这个关键词能搜得更全。

这几篇文章基本上把 SOURCE 阶段的注解实现和使用讲了,但是具体细节,比如用 javac 直接编译运行代码,javac 使用 jar 包,使用 maven 等三个方式如何运行注解处理器,倒是基本上蜻蜓点水,摸索了我好久才搞定了。关于注解处理器的知识,可以从如上三篇文章了解,本文主要讲注解的定义和运行。

我用的代码是摘抄至 JVM 进阶 — 浅谈注解处理器,它定义了一个 CheckGetter 的注解,用来检查一个类里面的字段,哪个没有 Getter 方法,没有的话编译就报错。不过我的和他的稍稍不同,他的代码定义没有放到 package 里面,我的放到 package 里面了,这样子的使用和执行又有了点不同。

首先,定义 CheckGetter 注解:

package com.shahuwang.processor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckGetter {
}

注意上面的代码,是放到 package com.shahuwang.processor 里面的,因此,先创建如下结构的文件夹

Processor
    —— com
       —— shuhuwang
           —— processor
               —— CheckGetter.java
           
           

注解已经定义好了,现在先来用一下这个注解:

package com.shahuwang.processor;

@CheckGetter
public class TestGetter {
    String name;
    String first;

    public String getName(){return this.name;}
}

这个类有两个字段,但是有一个字段没有设置 getter。

下面才是真正重要的代码,就是让 CheckGetter 这个注解真正能运行起来,发挥作用:

package com.shahuwang.processor;

import javax.annotation.processing.AbstractProcessor;
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.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.lang.model.element.Element;
import java.util.Set;

// 这个地方换了包名就需要改过来,否则 processor 就不会执行了, 这里是最需要注意的地方,千万注意!!!!!@SupportedAnnotationTypes("com.shahuwang.processor.CheckGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class CheckGetterProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(CheckGetter.class))) {for (VariableElement field : ElementFilter.fieldsIn(annotatedClass.getEnclosedElements())) {if(!containsGetter(annotatedClass, field.getSimpleName().toString())){processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            String.format("getter not found for'%s.%s'.", annotatedClass.getSimpleName(), field.getSimpleName()));
                }
            }
        }
        return false;
    }

    private static boolean containsGetter(TypeElement typeElement, String name){String getter = "get" + name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
        for(ExecutableElement executableElement: ElementFilter.methodsIn(typeElement.getEnclosedElements())){if(!executableElement.getModifiers().contains(Modifier.STATIC) &&
                    executableElement.getSimpleName().toString().equals(getter) &&
                    executableElement.getParameters().isEmpty()){return true;}

        }
        return false;
    }
}

上面这段代码要理解的概念有点儿多,实际上我现在也不是很懂,但是本文的目标是先让注解处理器跑起来,所以先不管。这里最重要也是折磨我最惨的地方,就是这一句 @SupportedAnnotationTypes("com.shahuwang.processor.CheckGetter"),你定义的注解在哪个 package 下,这里就要写完整了,如果写错了,注解处理器就不起作用了。

现在在 Processor 目录下,打开命令行,先编译 CheckGetterProcessor.java:javac com/shahuwang/processor/CheckGetterProcessor.java,编译好之后就可以使用了,再执行命令:javac -processor com.shahuwang.processor.CheckGetterProcessor com/shahuwang/processor/TestGetter.java,便可以看到这样的提示:

错误: getter not found for ‘TestGetter.first’.

上面这种方式,需要每次执行编译的时候,指定一下 processor 才能做到检查作用,那如果在团队开发中,希望自动执行一些检查,那可以用 SPI 服务提供者发现机制或者 maven 来实现。

SPI 服务提供者发现机制就是配置 META-INF 文件夹和里面的文件,告诉 Java 你要执行某个东西,这个东西的路径在哪里。再把编译好的 processor 和 META-INF 打包成 jar 包,就可以很方便使用了。文件夹和文件结构如下:

processor
    -com
       -shahuwang
          - processor
            - CheckGetterProcessor.class
    -META-INF
       -services
          - javax.annotation.processing.Processor

编译方式和上述一样。

javax.annotation.processing.Processor 是一个普通的文本,就是告知 java 一些关于注解处理器的配置,里面的内容如下:

com.shahuwang.processor.CheckGetterProcessor

就是告知这个注解处理器要用哪些个,如果有多个的话,可以一行一个。

然后在 processor 目录下,执行指令:jar -cvf processor.jar com META-INF, 形成了一个 processor.jar 的包,此时可以执行指令:

javac -cp processor.jar com/shahuwang/processor/TestGetter.java

就会自动执行注解处理器检查字段有没有 getter 方法了。

jar 包的这个方法,其实是把注解和注解的实现单独放一块作为一个插件来使用了,maven 也是如此的。

现实的开发中,还是用 maven 最多,先用指令创建一个 maven 项目:mvn archetype:generate -DgroupId=com.shahuwang -DartifactId=Processor -Dpackage=com.shahuwang.processor

然后在 pom.xml 的 <project> 层级下添加如下配置:

  <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
      <java.version>1.8</java.version>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.5.1</version>
          <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <encoding>UTF-8</encoding>
            <annotationProcessors>
              <annotationProcessor>
                com.shahuwang.processor.CheckGetterProcessor
              </annotationProcessor>
            </annotationProcessors>
          </configuration>
        </plugin>
      </plugins>
    </build>

代码目录结构如下:

Processor
    - src
        - main
            - java
                -com
                    -shahuwang
                        -processor
                            -CheckGetter.java
                            -CheckGetterProcessor.java
    - pom.xml

然后 Processor 目录下执行:mvn clean install, 这个项目就会被安装到本地的 maven 库里面。

再用 maven 新建一个 TestProcessor 项目,项目结构如下:

TestProcessor
    - src
        - main
            - java
                -com
                    -shahuwang
                        -TestGetter.java
                            
    - pom.xml

TestGetter.java 的代码上面有。

修改 pom.xml, 先在 <project> 下添加:

  <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
      <java.version>1.8</java.version>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.5.1</version>
          <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <encoding>UTF-8</encoding>
            <annotationProcessors>
              <annotationProcessor>
                com.shahuwang.processor.CheckGetterProcessor
              </annotationProcessor>
            </annotationProcessors>
          </configuration>
        </plugin>
      </plugins>
    </build>

再在 dependencies 添加:

   <dependency>  
        <groupId>com.shahuwang</groupId>
        <artifactId>Processor</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>  
  </dependencies>

也就是引入上面编译好的处理器的包。

现在,在 TestProcessor 目录下执行:

mvn compile

就会看到熟悉的提示:

错误: getter not found for ‘TestGetter.first’.

三种主流的注解处理器使用方式现在都搞懂怎么使用了。下一篇文章将会关注注解处理器的使用方法,各个注解处理器 API 的使用

正文完
 0