Java Annotation Processor(一)

简介: Java Annotation Processor

本篇内容意图介绍什么是Java Annotation Processor(注解处理器),我们可以用它来做什么?如何编写它?

基础知识

首先要说明的是:我们不是在讨论在运行时如何使用注解(运行时=应用程序运行的时间),而注解处理是发生在编译时(编译时间= Java编译器编译Java源代码的时间)。

  • 做什么?
    使用注解处理器我们可以在编译Java源代码时生成我们所想要的Java文件,最熟悉的例子便是我们经常使用的Lombok插件,当然,Lombok插件是直接在我们的字节码文件上进行修改,而我们今天介绍的是如何生成新的Java文件。

所以我们应该如何在编译时使用@annotation生成想要的Java文件呢?

案例

假设我们现在开设了一个水果店,里面有各种各样的水果,苹果,香蕉,橘子.....,当客人需要某种水果时,我们将返回对应水果的价格,于是我们有了以下的例子:

  • 水果接口
public interface Fruit {
    /**
     * 获取水果价格
     *
     * @return 水果价格
     */
    Float getPrice();
}
  • 苹果
public class Apple implements Fruit {
    @Override
    public Float getPrice() {
        return 3F;
    }
}
  • 香蕉
public class Banana implements Fruit{
    @Override
    public Float getPrice() {
        return 8F;
    }
}
  • 由于类型会有很多,我们需要一个工厂类
public class FruitFactory {
  public static Fruit create(String id) {
    if("banana".equals(id)) {
      return new Banana();
    }
    if("apple".equals(id)) {
      return new Apple();
    }
    throw new IllegalArgumentException("Unknown id = " + id);
  }
}
  • 水果订单
public class OrderFruit {
    /**
     * 获取水果价格
     *
     * @param fruitName 水果名称
     * @return 水果价格
     */
    public Float order(String fruitName){
        return FruitFactory.create(fruitName).getPrice();
    }
}

显然,以上是我们工厂模式的经典写法,看上去多么美好,那么还有什么问题吗?

假设我们多加了一种水果,那么就要修改FruitFactory每次加一种水果,每次都要修改,当然你可能认为这无伤大雅,我也是如此认为,不过毕竟这只是个案例不是吗?我们就这样姑且认为它有问题吧~

如何改进我们的案例?

假设我们像Lombok这样有一个注解,比如叫做@Factory,当我们在水果类上加上该注解,编译时便会自动通过这些加了@Factory注解的水果类生成一个水果工厂,就像这样:

@Factory
public class Apple implements Fruit {
    @Override
    public Float getPrice() {
        return 3F;
    }
}
@Factory(id = "banana", type = Fruit.class)
public class Banana implements Fruit{
    @Override
    public Float getPrice() {
        return 8F;
    }
}
public class FruitFactory {
  public static Fruit create(String id) {
    if("banana".equals(id)) {
      return new Banana();
    }
    if("apple".equals(id)) {
      return new Apple();
    }
    throw new IllegalArgumentException("Unknown id = " + id);
  }
}

注意,这个FruitFactory是在编译时期自动生成的!

那么,怎么才能做到这样的效果呢?该我们的Java Annotation Processor登场啦!

Java Annotation Processor

AbstractProcessor

首先,每一个自己扩展的处理器都需要继承AbstractProcessor,类似这样

package com.example;
public class MyProcessor 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(ProcessingEnvironment env)注意:每个注解处理器类都必须有一个空的构造函数。但是,有一个特殊的init()方法,由注解处理工具使用ProcessingEnviroment作为参数来调用。ProcessingEnviroment提供了一些有用的util类ElementsTypes以及Filer。稍后我们将使用它们。
  • process(Set annotations, RoundEnvironment env):这是每个处理器的一种方法。在这里,可以编写代码来扫描,评估和处理注解以及生成Java文件。使用RoundEnviroment传递的参数作为参数,可以查询带有特定注解的元素,我们将在后面看到。
  • getSupportedAnnotationTypes():在这里,必须指定此注解处理器应为其注册的注解。请注意,返回类型是一组字符串,其中包含要使用此注解处理器处理的注解类型的全限定名称。换句话说,在此处定义要为其注册注解处理器的注解
  • getSupportedSourceVersion():用于指定您使用的Java版本,一般推荐使用SourceVersion.latestSupported()

如何注册自己的Processor?

我们可能会有这样的问题:“我如何向Javac注册MyProcessor?”。我们必须提供一个**.jar**文件。与其他任何.jar文件一样,将(已编译的)注解处理器打包在该文件中。此外,还必须打包一个特殊的.jar文件中位于META-INF / services中的名为javax.annotation.processing.Processor的文件,因此.jar文件的结构如下所示:

MyProcessor.jar
  - com
    - example
      - MyProcessor.class
  - META-INF
    - services
      - javax.annotation.processing.Processor

文件javax.annotation.processing.Processor(包装在MyProcessor.jar中)的内容是一个列表,其中包含处理器的合格类名,其中用换行符作为分隔符:

com.example.MyProcessor

在构建路径中使用MyProcessor.jar时,javac会自动检测并读取javax.annotation.processing.Processor文件,并将MyProcessor注册为注解处理器。

当然,这只是作为一个小知识,实际使用时我们可以通过google的@AutoService注解来自动进行该步骤

开始改进我们的案例

思考一下,如果需要生成我们的FruitFactory,我们需要什么?

  • 类名,-> FruitFactory(作为一个工厂的处理器,我们自然不能只支持水果工厂嘛)
  • 工厂方法
  • 工厂方法的返回值
  • 工厂方法的参数
  • 工厂方法中的代码块
  • 每个水果的全限定类名
  • 根据参数返回相应的水果对象

大概就是这些了,我们现在开始编写代码吧!

  • @Factory注解
/**
 * 标记为一个工厂组件
 *
 * @author Zijian Liao
 * @since 1.0.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Factory {
    /**
     * 组件的id, 用于确认生成哪个组件
     */
    String id();
    /**
     * 工厂的类型
     */
    Class<?> type();
}
  • 各个修改后的水果
@Factory(id = "apple", type = Fruit.class)
public class Apple implements Fruit {
    @Override
    public Float getPrice() {
        return 3F;
    }
}
@Factory(id = "banana", type = Fruit.class)
public class Banana implements Fruit{
    @Override
    public Float getPrice() {
        return 8F;
    }
}
  • 注解处理器
package com.my.annotation.process.processor;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.my.annotaion.process.annotation.Factory;
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.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.Set;
/**
 * 工厂类生成器
 *
 * @author Zijian Liao
 * @since 1.0.0
 */
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
    /**
     * 文件写入器,用于生成class文件
     */
    private Filer filer;
    /**
     * 消息发送器,用于打印消息
     */
    private Messager messager;
    /**
     * 元素操作工具
     */
    private Elements elementUtils;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.filer = processingEnv.getFiler();
        this.messager = processingEnv.getMessager();
        this.elementUtils = processingEnv.getElementUtils();
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return ImmutableSet.of(Factory.class.getCanonicalName());
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try{
            // 获取到标记了@Factory的类
            Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(Factory.class);
            for (Element element : elementsAnnotatedWith) {
                FactoryHandler.putIfAbsent(new FactoryClassInfo(element));
            }
            FactoryHandler.generateJavaFile(elementUtils, filer );
        }catch (IOException e){
            error(null, e.getMessage());
        }finally {
            // 重要:最后需要把存储的信息清除
            // 原因:注解处理按一系列回合进行的,当第一个回合生成了新的文件,那么processor便会使用这些的文件进行第二个回合
            // 如果我们不清除这些第一个回合存储的信息,那么在第二个回合将会被重复使用!这将引起文件重复创建的错误!
            FactoryHandler.clear();
        }
        return true;
    }
    /**
     * 打印错误信息
     *
     * @param e 用作位置提示的元素
     * @param message 错误信息
     */
    private void error(Element e, String message) {
        messager.printMessage(Diagnostic.Kind.ERROR, message, e);
    }
}

其中@AutoService注解处理器由Google开发,并生成META-INF/services/javax.annotation.processing.Processor文件。这是因为我们需要这个文件来注册自己的Processor(前面提到过)



目录
相关文章
|
6月前
|
安全 Java 编译器
Java其他: 什么是Java中的注解(Annotation)?
Java其他: 什么是Java中的注解(Annotation)?
78 0
|
SQL XML SpringCloudAlibaba
Java独有特性:注解(annotation)
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。它本身并不起任何作用,可以说有它没它都不影响程序的正常运行,注解的作用在于**「注解的处理程序」**,注解处理程序通过捕获
169 0
|
6月前
|
算法 安全 Ubuntu
8 种 Java 内存溢出之八 -Kill process or sacrifice child
8 种 Java 内存溢出之八 -Kill process or sacrifice child
|
6月前
|
Java 编译器 开发者
Java注解(Annotation)技术深入解析
Java注解(Annotation)技术深入解析
447 1
|
Java Maven
【异常】java: Internal error in the mapping processor: java.lang.NullPointerException
【异常】java: Internal error in the mapping processor: java.lang.NullPointerException
304 0
|
6月前
|
Java
【Java】注解(Annotation)
【Java】注解(Annotation)
47 0
|
安全 Java 编译器
Java中的String实例化、Annotation注解类、继承的多态和Object类(附带相关面试题)
1.java中String两种实例化对象2.Annotation注解类 3.继承的多态 4.Object类
116 0
Java中的String实例化、Annotation注解类、继承的多态和Object类(附带相关面试题)
|
设计模式 缓存 Java
Java反射(反射与代理设计模式、反射与Annotation、自定义Annotation、反射整合工厂设计模式和代理设计模式)
1.反射与代理设计模式,2.反射与Annotation,3.自定义Annotation,4.Annotation整合工厂设计模式和代理设计模式
70 0
|
Java 测试技术 程序员
Java的注解(Annotation)
Java的注解(Annotation)
133 0
|
SQL Java 数据库连接
Java 注解 Annotation自定义实战
Java 注解 Annotation自定义实战
129 0