Java 注解 Annotation

录:

 1、基本 Annotation
    1.1、限定重写父类方法:@Override
    1.2、标记已过时:@Deprecated
    1.3、抑制编译器警告:@SuppressWarnings
    1.4、Java7 的“堆污染”警告和 @SafeVarargs
    1.5、Java8 新增的用于标识(限定)函数式接口:@FunctionalInterface
2、JDK 的元 Annotation
    2.1、注解的保留策略限定:@Retention
    2.2、注解的作用目标限定:@Target
    2.3、@Documented
    2.4、@Inherited
    2.5、Java8 新增的重复注解:@Repeatable
3、自定义注解
    3.1、定义 Annotation
    3.2、提取 Annotation 信息
4、使用 “运行时注解“ 的例子
5、“编译时注解”
    5.1、"编译时注解" 的简单使用:编译时打印字段字符串
    5.2、将注解和注解处理器打成工具 jar 包使用
    5.3、使用 ”编译时注解“ 的例子:模拟 lombok 注解 @Data
    5.4、eclipse 创建 maven 项目,使用自己写的 Annotation Processor
    

  从 JDK5 开始,Java 增加了对元数据(MetaData)的支持,也就是 Annotation。Annotation 其实就是代码里面的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

1、基本 Annotation    <--返回目录

  Java 提供的 5 个基本 Annotation 如下:

  1)Override,2)Deprecated, 3)SuppressWarnings, 4)SafeVarargs, 5)FunctionalInterface

1.1、限定重写父类方法:@Override    <--返回目录

  @Override 的用法举例:

public class Animal {
    public void eat() {
        System.out.println("Animal 的 eat() 方法");
    }
}

class Cat extends Animal {
    // 使用 @Override 注解指定下面的 eat() 方法必须是重写父类方法
    @Override
    public void eat() {
        // super.eat();
        System.out.println("Cat 的 eat() 方法");
    }
}

  @Override 的源码:

package java.lang;
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

  @Target(ElementType.METHOD):表示 @Override 注解只能标注在方法上。

  @Retention(RetentionPolicy.SOURCE):表示 @Override 注解只会保留在源代码中,编译器直接丢弃(class 文件中不保留)。

1.2、标记已过时:@Deprecated    <--返回目录

  @Deprecated 用于表示某个程序元素(类、方法等)已过时,当程序使用已过时的类、方法时,编译器将会给出警告。

  @Deprecated 使用举例:使用已过时的方法 eat(),IDE 会在该方法上加上中划线 “—” 的标识

1.3、抑制编译器警告:@SuppressWarnings    <--返回目录

  @SuppressWarnings 使用举例:

 

  

1.4、Java7 的“堆污染”警告和 @SafeVarargs    <--返回目录

  下面的代码引发错误的原因成为“堆污染”,当把一个不带泛型的对象赋给一个带泛型的对象时,往往就会发生这种“堆污染”。

public static void main(String[] args) {
    List list = new ArrayList<Integer>();
    list.add(10);
    
    // 将 list 赋给 list2,编译运行都完全正常
    List<String> list2 = list;
    
    // 但只要访问list2里面的元素,就会引起运行时异常
    /* Exception in thread "main" java.lang.ClassCastException: 
     java.lang.Integer cannot be cast to java.lang.String
     at com.oy.Animal.main(Animal.java:11) */
    System.out.println(list2.get(0));
}

  对于形参个数可变的方法,该形参又是泛型,将更容易导致“堆污染”。如下面的代码,listArray 是可变参数,相当于数组,但是Java不支持泛型数组,所以只能把 List<String>... 当成 List[]来处理,这样就发生了“堆污染”,当在访问 listArray 的元素时引起运行时异常。

public class Demo {
    
    public static void main(String[] args) {
        method(Arrays.asList("aaa"), Arrays.asList("bbb"));
    }
    
    public static void method(List<String>... listArray) {
        List[] array = listArray;
        
        array[0] = Arrays.asList(1);
        // java.lang.Integer cannot be cast to java.lang.String
        System.out.println(listArray[0].get(0));
    }
}

   但有时候我们知道代码没有问题,我们不想看到这个警告,可以使用 @SafeVarargs 来压制这个警告

1.5、Java8 新增的用于标识(限定)函数式接口:@FunctionalInterface    <--返回目录

  如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口。

  @FunctionalInterface 注解只是告诉编译器检查这个接口,保证这个接口只能由一个抽象方法,否则就会报编译出错。

2、JDK 的元 Annotation    <--返回目录

  JDK 提供了 5 个Meta Annotation(元注解),用于修饰其他的 Annotation 定义,即:

  1) Retention, 2) Target, 3) Documented, 4) Inherited, 5) Repeatable。

2.1、注解的保留策略限定:@Retention    <--返回目录

  @Retention 源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

  @Retention 只能用于修饰 Annotation 定义,用于指定被修饰的 Annotation 可以保留多长时间,@Retention 包含一个 RetentionPolicy 类型的 value 成员变量,所以使用 @Retention 时必须为该 value 成员变量指定值。value 成员变量的值只能是如下三个:

  RetentionPolicy.SOURCE: Annotation 只保留在源代码中,编译器直接丢弃这种注解。

  RetentionPolicy.CLASS: 编译器将把 Annotation 记录在 class 文件中。当运行 Java 程序时,JVM 不可获取该 Annotation 信息。这是默认值。

  RetentionPolicy.RUNTIME: 编译器将把 Annotation 记录在 class 文件中。当运行 Java 程序时,JVM 也可获取该 Annotation 信息,程序可以通过反射获取该 Annotation 信息。

  定义一个 “编译时注解”:

// RetentionPolicy.SOURCE: 表示此注解只保留在源代码中,编译器直接丢弃这种注解
// 通常称这种注解为 “编译时注解”
@Retention(RetentionPolicy.SOURCE)
public @interface MyTag {
}

  定义一个 “运行时注解”:

// RetentionPolicy.SOURCE: 编译器将把 Annotation 记录在 class 文件中。当运行 Java 程序时,JVM 也可获取该 Annotation 信息,
// 程序可以通过反射获取该 Annotation 信息。
// 通常称这种注解为 “运行时注解”
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
}

2.2、注解的作用目标限定:@Target    <--返回目录

  @Target 源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

  @Target 包含一个 ElementType[] 类型的 value 成员变量,该成员变量的值只能为如下:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

  ANNOTATION_TYPE:限定该注解只能用于修饰 Annotation。比如 @Target 定义时就使用的是这个限定。

2.3、@Documented    <--返回目录

  @Documented 是一个标记注解,没有成员。用于指定此注解是否被javadoc 工具提取成文档。如果定义注解时加上了 @Documented,则使用该注解修饰的程序元素的 API 文档中将会包含该注解说明。

@Documented
public @interface MyTag {
}

2.4、@Inherited    <--返回目录

  @Inherited 用于指定被它修改的 Annotation 将具有继承性---如果某个类使用了@Xxx 注解(定义该注解时使用了 @Inherited 修饰),则其子类将自动被 @Xxx 修饰。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
}
@MyTag
class Base {
    
}

// 这个类继承了 Base类
// 并未直接使用 @MyTag 修饰,
public class TestBase extends Base {
    public static void main(String[] args) {
        // isAnnotationPresent: 判断此程序元素是否被某个注解修饰
        // 注意:@MyTag 定义时指定保留策略为 RUNTIME
        System.out.println(TestBase.class.isAnnotationPresent(MyTag.class)); // true
    }
}

2.5、Java8 新增的重复注解:@Repeatable    <--返回目录

  某些情况下,一个程序元素需要使用多个同一种类型的注解。@Repeatable元注解,顾名思义,重复注解,就是在声明创建注解的时候,指定该注解可以被同一个程序元素多次使用。

  使用 @Repeated 举例:

  定义一个注解:

package com.oy;

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

@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Role {
    // 为该注解定义了一个成员变量
    String value();
}

  定义一个 “容器” 注解

package com.oy;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Roles {
    Role[] value();
}

  测试

package com.oy;

import java.lang.annotation.Annotation;

// 重复使用 @Role 注解 @Role(
"admin") @Role("user") public class Demo { public static void main(String[] args) { Annotation[] annotations = Demo.class.getAnnotations(); for (Annotation a : annotations) { // @com.oy.Roles(value=[@com.oy.Role(value=admin), @com.oy.Role(value=user)]) System.out.println(a); } // getDeclaredAnnotationsByType: 可以获取多个重复注解 Role[] array = Demo.class.getDeclaredAnnotationsByType(Role.class); for (Role r: array) { // @com.oy.Role(value=admin) // @com.oy.Role(value=user) System.out.println(r); System.out.println(r.value()); } } }

3、自定义注解    <--返回目录

3.1、定义 Annotation    <--返回目录

   定义注解时,定义了一个成员变量 name, 并且指定了默认值

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

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyTag {
    String name() default "hello";
}

 根据 Annotation 是否包含成员变量,将 Annotation 分为如下两类:

  1)标记 Annotation:没有定义成员变量。这种 Annotation 仅利用自身的存在与否来提供信息,如前面介绍的 @Override等。

  2)元数据 Annotation:包含成员变量。

3.2、提取 Annotation 信息    <--返回目录

   使用 Annotation 修饰了类、方法、成员变量等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的工具来提取并处理 Annotation 信息。

  Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有注解的父接口。Java5 在java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代码程序中可以接受注解的程序元素。该接口主要有如下几个实现类:

  1)Class: 类定义;

  2)Constructor: 构造器定义;

  3)Field:类的成员变量定义;

  4)Method:类的方法定义;

  5)Package:类的包定义;

  java.lang.reflect 包下主要包含一些实现反射功能的工具类,从 Java5 开始,java.lang.reflect 包所提供的反射 API 增加了读取运行时 Annotation 的能力。只有当定义 Annotation 时使用了 @Retention(RerentionPolicy.RUNTIME) 修饰,该 Annotation 才会在运行时可见,JVM 才会在加载 class 字节码文件时读取保存在 class 文件中的 Annotation 信息。

  反射获取注解信息的 API:

  1)<A extends Annotation> A getAnnotation(Class<a> annotationClass): 返回该程序元素上存在的指定类型的注解,如果指定的注解不存在,返回null;

  2)<A extends Annotation> A getDeclaredAnnotation(Class<a> annotationClass): 这是 Java8 新增的方法,该方法尝试获取直接修饰该程序元素的指定类型的注解,如果指定的注解不存在,返回null;

  3)Annotation[] getAnnotations(): 返回该程序元素上存在的所有注解;

  4)Annotation[] getDeclaredAnnotations(): 返回直接修饰该程序元素的所有注解;

  5)boolean isAnnotationPresent(Class<? extends Annotation) annotationClass): 判断该程序元素上是否存在指定类型的注解,存在返回 true,否则返回 false;

  6)<A extends Annotation> A[] getAnnotationsByType(class<A> annotationClass): 与 getAnnotation() 功能类似,但是可以获取指定类型的重复注解 ;

  7)<A extends Annotation> A[] getDeclaredAnnotationsByType(class<A> annotationClass): 与 getDeclaredAnnotation() 类似,但是可以获取直接修复该程序元素的指定类型的重复注解 ;

4、使用 “运行时注解“ 的例子    <--返回目录

   自定义注解 @Test

package com.oy;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    boolean value();
}

  测试使用该注解:

package com.oy;

import java.lang.reflect.Method;

public class Demo {

    public static void main(String[] args) {
        for (Method method : Demo.class.getMethods()) {
            // 方法上没有标注 @Test 注解,不处理
            if (!method.isAnnotationPresent(Test.class)) continue;
            
            // @Test(false) 不处理
            Test test = method.getAnnotation(Test.class);
            if (!test.value()) continue;
            
            // 执行 @Test(true) 标注的方法
            try {
                method.invoke(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Test(true)
    public static void test1() {
        System.out.println("test1...");
    }

    @Test(false)
    public static void test2() {
        System.out.println("test2...");
    }
    
    public static void test3() {
        System.out.println("test3...");
    }
}

  更多使用 “运行时注解“ 的例子可以参考博客:

  1)springboot + 注解 + 拦截器 + JWT 实现角色权限控制

  2)Java + SpringBoot + 自定义注解 + 拦截器实现 BASIC 认证

5、“编译时注解”    <--返回目录

  RetentionPolicy.SOURCE: Annotation 只保留在源代码中,编译器直接丢弃这种注解。这种注解通常称为 “编译时注解”,编译器在对源码进行编译时读取注解信息,做一些额外的处理

   APT:Annotation Processing Tool, 是一种注解处理工具,它对源代码进行检查,并找出源文件所包含的注解信息,然后针对注解信息进行额外的处理。使用 APT 的主要目的是简化开发者的工作量,因为 APT 可以在编译程序源代码的同时生成一些附加文件(比如源文件、类文件、程序发布描述文件等),这些附属文件的内容也都与源代码有关。换句话说,使用 APT 可以替代传统的对代码信息和附属文件的维护工作。

  Java 提供的 javac.exe 工具有一个 -processor 选项,该选项可以指定一个 Annotation 处理器,如果在编译源文件时通过该选项指定了 Annotation 处理器,那么这个 Annotation 处理器将会在编译时提取并处理 Java 源文件种的 Annotation信息。

  每个 Annotation 处理器都需要实现 javax.annotation.processing 包下的 Processor 接口。不过实现该接口要实现它里面所有的方法,因此通常会采用继承 AbstractProcessor 的方式来编写 Annotation 处理器。一个 Annotation 处理器可以处理一个或多个 Annotation 类型。一个继承 AbstractProcessor 的举例如下:

import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
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.TypeElement;
 
public class MyProcessor extends AbstractProcessor {
 
    @Override
    public boolean process(Set<? extends TypeElement> annoations,
            RoundEnvironment env) {
        return false;
    }
 
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add("com.example.MyAnnotation");
        return annotataions;
    }
 
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
 
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }
 
}

  其中,init(ProcessingEnvironment processingEnv):所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init(),它会被注解处理工具调用,以ProcessingEnvironment作为参数。ProcessingEnvironment 提供了一些实用的工具类Elements, Types和Filer。

  process(Set annoations, RoundEnvironment env):这类似于每个处理器的main()方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用RoundEnvironment 参数,你可以查询被特定注解标注的元素。

  getSupportedAnnotationTypes():在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解。

  getSupportedSourceVersion():用来指定你使用的 java 版本。通常你应该返回SourceVersion.latestSupported()。不过,如果你有足够的理由坚持用 java 6 的话,你也可以返回SourceVersion.RELEASE_6。
  关于getSupportedAnnotationTypes()和getSupportedSourceVersion()这两个方法,你也可以使用相应注解进行代替。代码如下:

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.MyAnnotation")
public class MyProcessor extends AbstractProcessor {
....

  

5.1、"编译时注解" 的简单使用:编译时打印字段字符串    <--返回目录

  分别编译 DataTest 和 MyProcessor, 然后使用 "javac -process 注解处理器 类名.java" 命令编译使用 @DataTest 注解的类

    DataTest 注解的定义

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

@Retention(RetentionPolicy.SOURCE)
public @interface DataTest {
}

  注解处理器 MyProcessor

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
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.TypeElement;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes("DataTest")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyProcessor extends AbstractProcessor {
    private ProcessingEnvironment processingEnv;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        this.processingEnv = processingEnv;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "MyProcessor  process()...");
        return false;
    }

}

  测试类 TestDemo

@DataTest
public class TestDemo {
    public static void main(String[] args) {
        System.out.println("main run...");
    }
}

5.2、将注解和注解处理器打成工具 jar 包使用    <--返回目录

上面的例子简单演示了 “javac -processor 注解处理器 类名.java” 命令的使用。开发中可能需要将注解和注解处理器打成工具 jar 包使用。

  步骤1:新建一个 maven 工程:

    pom.xml 文件

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.oy</groupId>
    <artifactId>MyAnnotationUtils</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.squareup/javawriter -->
        <dependency>
            <groupId>com.squareup</groupId>
            <artifactId>javawriter</artifactId>
            <version>2.5.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.auto.service/auto-service -->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc5</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
View Code

  DataTest

package com.oy.annotation;

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

@Retention(RetentionPolicy.SOURCE)
public @interface DataTest {

}
View Code

  MyProcessor

package com.oy.annotation;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
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.TypeElement;
import javax.tools.Diagnostic;

import com.google.auto.service.AutoService;

@SupportedAnnotationTypes("com.oy.annotation.DataTest")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    private ProcessingEnvironment processingEnv;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        this.processingEnv = processingEnv;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "MyProcessor process...");
        return false;
    }

}
View Code

  步骤2:选择项目,右键 ==> Run as => Maven build... => clean package, 会在项目根目录的 target 目录下生成 MyAnnotationUtils.jar, 从而可以在其他项目中使用。

    步骤三:新建一个目录,将MyAnnotaionUtils.jar 复制到这个目录

     解压这个jar会发现,在 META-INF/services 下面会有个文件 javax.annotation.processing.Processor,是 @AutoService 注解帮助生成的。

  步骤四:编写测试类 TestDemo

import com.oy.annotation.DataTest;

@DataTest
public class TestDemo {
    public static void main(String[] args) {
        System.out.println("main run...");
    }
}
View Code

  步骤五:执行 "java -cp MyAnnotationUtils.jar TestDemo.java" 命令

5.3、使用 ”编译时注解“ 的例子:模拟 lombok 注解 @Data    <--返回目录

参考:基于注解处理器开发自动生成getter和setter方法的插件

 

   pom.xml与上面的例子一样,都引入了依赖'com.squareup:javawriter:2.5.1' 和 ‘com.google.auto.service:auto-service:1.0-rc5’

  Data

package com.oy.annotation;

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

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Data {
 
}
View Code

  DataAnnotationProcessor

package com.oy.annotation;

import java.io.IOException;
import java.io.Writer;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
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.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import com.google.auto.service.AutoService;
import com.squareup.javawriter.JavaWriter;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.oy.annotation.Data"})
@AutoService(Processor.class)
public class DataAnnotationProcessor extends AbstractProcessor {
    private Messager messager; // 用于打印日志
    private Elements elementUtils; // 用于处理元素
    private Filer filer; // 用来创建java文件或者class文件

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

//    @Override
//    public SourceVersion getSupportedSourceVersion() {
//        return SourceVersion.latestSupported();
//    }
//
//    @Override
//    public Set<String> getSupportedAnnotationTypes() {
//        Set<String> set = new HashSet<>();
//        set.add(Data.class.getCanonicalName());
//        return Collections.unmodifiableSet(set);
//    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.NOTE, "-----开始自动生成源代码");
        try {
            // 标识符
            boolean isClass = false;
            // 类的全限定名
            String classAllName = null;
            // 返回被注释的节点
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Data.class);
            Element element = null;
            for (Element e : elements) {
                // 如果注释在类上
                if (e.getKind() == ElementKind.CLASS && e instanceof TypeElement) {
                    TypeElement t = (TypeElement) e;
                    isClass = true;
                    classAllName = t.getQualifiedName().toString();
                    element = t;
                    break;
                }
            }
            // 未在类上使用注释则直接返回,返回false停止编译
            if (!isClass) {
                return true;
            }
            // 返回类内的所有节点
            List<? extends Element> enclosedElements = element.getEnclosedElements();
            // 保存字段的集合
            Map<TypeMirror, Name> fieldMap = new HashMap<>();
            for (Element ele : enclosedElements) {
                if (ele.getKind() == ElementKind.FIELD) {
                    // 字段的类型
                    TypeMirror typeMirror = ele.asType();
                    // 字段的名称
                    Name simpleName = ele.getSimpleName();
                    fieldMap.put(typeMirror, simpleName);
                }
            }
            // 生成一个Java源文件(文件名为 类名.java)
            JavaFileObject sourceFile = filer.createSourceFile(getClassName(classAllName));
            // 写入代码
            createSourceFile(classAllName, fieldMap, sourceFile.openWriter());
            // 手动编译
            compile(sourceFile.toUri().getPath());
        } catch (IOException e) {
            messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
        }
        messager.printMessage(Diagnostic.Kind.NOTE, "-----完成自动生成源代码");
        return true;
    }

    private void createSourceFile(String classAllName, Map<TypeMirror, Name> fieldMap, Writer writer)
            throws IOException {
        // 生成源代码
        JavaWriter jw = new JavaWriter(writer);
        jw.emitPackage(getPackage(classAllName));
        jw.beginType(getClassName(classAllName), "class", EnumSet.of(Modifier.PUBLIC));
        for (Map.Entry<TypeMirror, Name> map : fieldMap.entrySet()) {
            String type = map.getKey().toString();
            String name = map.getValue().toString();
            // 字段
            jw.emitField(type, name, EnumSet.of(Modifier.PRIVATE));
        }
        for (Map.Entry<TypeMirror, Name> map : fieldMap.entrySet()) {
            String type = map.getKey().toString();
            String name = map.getValue().toString();
            // getter
            jw.beginMethod(type, "get" + humpString(name), EnumSet.of(Modifier.PUBLIC)).emitStatement("return " + name)
                    .endMethod();
            // setter
            jw.beginMethod("void", "set" + humpString(name), EnumSet.of(Modifier.PUBLIC), type, "arg")
                    .emitStatement("this." + name + " = arg").endMethod();
        }
        jw.endType().close();
    }

    /**
     * 编译文件
     * 
     * @param path
     * @throws IOException
     */
    private void compile(String path) throws IOException {
        // 拿到编译器
        JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
        // 文件管理者
        StandardJavaFileManager fileMgr = complier.getStandardFileManager(null, null, null);
        // 获取文件
        Iterable units = fileMgr.getJavaFileObjects(path);
        // 编译任务
        JavaCompiler.CompilationTask t = complier.getTask(null, fileMgr, null, null, null, units);
        // 进行编译
        t.call();
        fileMgr.close();
    }

    /**
     * 驼峰命名
     *
     * @param name
     * @return
     */
    private String humpString(String name) {
        String result = name;
        if (name.length() == 1) {
            result = name.toUpperCase();
        }
        if (name.length() > 1) {
            result = name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        return result;
    }

    /**
     * 从带包类名读取类名
     * 
     * @param classAllName 带包类名
     * @return
     */
    private String getClassName(String classAllName) {
        String result = classAllName;
        if (classAllName.contains(".")) {
            result = classAllName.substring(classAllName.lastIndexOf(".") + 1);
        }
        return result;
    }

    /**
     * 从带包类名读取包名
     * 
     * @param classAllName 带包类名
     * @return
     */
    private String getPackage(String classAllName) {
        String result = classAllName;
        if (classAllName.contains(".")) {
            result = classAllName.substring(0, classAllName.lastIndexOf("."));
        } else {
            result = "";
        }
        return result;
    }
}
View Code

  测试使用:

5.4、eclipse 创建 maven 项目,使用自己写的 Annotation Processor    <--返回目录

  首先将自己写的 Annotation Processor(maven 项目)打包到本地仓库:

   新建一个 mavenDemo 项目,在 pom.xml 添加对 自己写的 Annotation Processor 的依赖:

<dependency>
    <groupId>com.oy</groupId>
    <artifactId>MyAnnotationUtils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

然后,选中 MavenDemo 项目,右键-> run as -> maven build... -> clean compile -> 在 target 目录下生成如下文件:

这个例子会报 com.oy.Demo 类重复的错,因为原来的Demo 类没删,所以重复了。

 参考:

  1)《疯狂 Java 讲义》--李刚

  2)基于注解处理器开发自动生成getter和setter方法的插件

  3)javawriter 的使用:生成Java源文件 (javawriter, javapoet, codemodel)

  4)Maven中调试Annotation Processor

原文地址:https://www.cnblogs.com/xy-ouyang/p/12670061.html