乐趣区

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

上一篇文章简单讲了下 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 的使用

退出移动版