java冷知识:javac AbstractProcessor详解

目录
  • 它可以做什么?
  • Processor
  • AbstractProcessor
  • 源码
  • google的 auto-service
  • javapoet
  • 你喜欢的lombok实现原理是怎样的呢?

它可以做什么?

它做的事情当然是生成新类或修改原始的类,比如你遇到这样的情况下就可以使用:

  • 反射好慢,曾见过一个大厂大量是Gson,由于Gson序列化时大量使用了反射,每一个field,每一个get、set都需要用反射,由此带来了性能问题。解决方法就是使用它尽量减少反射(替换成JSONObject)
  • 生成代码,只要是有注解的地方都可以读取,总之很多(有些android orm框架)

Processor

javax.annotation.processing.Processor 这个接口将提供注解处理,它遵循SPI规约进行拓展,jdk默认就有很多处理器的实现。

AbstractProcessor

注解处理器是最重要的拓展处理类了。

注意:请确认JAVA的环境变量已经配置成功,并且把tools.jar(它源于此包)加入到自己电脑的环境变量中

ProcessingEnvironment 是一个注解处理工具的集合
Element 是一个接口,表示一个程序元素,它可以是包、类、方法或者一个变量。Element已知的子接口有:

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

源码

重点关注process方法

// 源于javax.annotation.processing;
public abstract class AbstractProcessor implements Processor {
    // 集合中指定支持的注解类型的名称(这里必须时完整的包名+类名)
    public Set<String> getSupportedAnnotationTypes() {
            SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
            if  (sat == null) {
                if (isInitialized())
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                             "No SupportedAnnotationTypes annotation " +
                                                             "found on " + this.getClass().getName() +
                                                             ", returning an empty set.");
                return Collections.emptySet();
            }
            else
                return arrayToSet(sat.value());
        }
    // 指定当前正在使用的Java版本
    public SourceVersion getSupportedSourceVersion() {
        SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
        SourceVersion sv = null;
        if (ssv == null) {
            sv = SourceVersion.RELEASE_6;
            if (isInitialized())
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                         "No SupportedSourceVersion annotation " +
                                                         "found on " + this.getClass().getName() +
                                                         ", returning " + sv + ".");
        } else
            sv = ssv.value();
        return sv;
    }
    // 初始化处理器
    public synchronized void init(ProcessingEnvironment processingEnv) {
        if (initialized)
            throw new IllegalStateException("Cannot call init more than once.");
        Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");

        this.processingEnv = processingEnv;
        initialized = true;
    }
    /**
     * 这些注解是否由此 Processor 处理,该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理
     */
    public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);
}

实现一个打印可以API的功能

由于本人是maven环境,以此展开讲

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <!--Disable annotation processing for ourselves-->
                <!--<compilerArgument>-proc:none</compilerArgument>-->
            </configuration>
        </plugin>
    </plugins>
</build>

步骤1:实现一个注解处理器

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiAnnotation {
    String author() default "alex.chen";
    String date();
    int version() default 1;
}
@SupportedAnnotationTypes({"com.kxtx.annotation.ApiAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    //类名的前缀、后缀
    public static final String SUFFIX = "AutoGenerate";
    public static final String PREFIX = "My_";
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        for (TypeElement typeElement : annotations) {
            for (Element e : env.getElementsAnnotatedWith(typeElement)) {
                //打印
                messager.printMessage(Diagnostic.Kind.WARNING, "Printing:" + e.toString());
                messager.printMessage(Diagnostic.Kind.WARNING, "Printing:" + e.getSimpleName());
                messager.printMessage(Diagnostic.Kind.WARNING, "Printing:" + e.getEnclosedElements().toString());

                //获取注解
                ApiAnnotation annotation = e.getAnnotation(ApiAnnotation.class);
                //获取元素名并将其首字母大写
                String name = e.getSimpleName().toString();
                char c = Character.toUpperCase(name.charAt(0));
                name = String.valueOf(c + name.substring(1));
                //包裹注解元素的元素, 也就是其父元素, 比如注解了成员变量或者成员函数, 其上层就是该类
                Element enclosingElement = e.getEnclosingElement();
                //获取父元素的全类名,用来生成报名
                String enclosingQualifiedname;
                if (enclosingElement instanceof PackageElement) {
                    enclosingQualifiedname = ((PackageElement) enclosingElement).getQualifiedName().toString();
                } else {
                    enclosingQualifiedname = ((TypeElement) enclosingElement).getQualifiedName().toString();
                }
                try {
                    //生成包名
                    String generatePackageName = enclosingQualifiedname.substring(0, enclosingQualifiedname.lastIndexOf("."));
                    // 生成的类名
                    String genarateClassName = PREFIX + enclosingElement.getSimpleName() + SUFFIX;
                    //创建Java 文件
                    JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
                    // 在控制台输出文件路径
                    messager.printMessage(Diagnostic.Kind.WARNING, "Printing: " + f.toUri());
                    Writer w = f.openWriter();
                    try {
                        PrintWriter pw = new PrintWriter(w);
                        pw.println("package " + generatePackageName + ";");
                        pw.println("\npublic class " + genarateClassName + " { ");
                        pw.println("\n    /** 打印值 */");
                        pw.println("    public static void print" + name + "() {");
                        pw.println("        // 注解的父元素: " + enclosingElement.toString());
                        pw.println("        System.out.println(\"代码生成的路径: " + f.toUri() + "\");");
                        pw.println("        System.out.println(\"注解的元素: " + e.toString() + "\");");
                        pw.println("        System.out.println(\"注解的版本: " + annotation.version() + "\");");
                        pw.println("        System.out.println(\"注解的作者: " + annotation.author() + "\");");
                        pw.println("        System.out.println(\"注解的日期: " + annotation.date() + "\");");

                        pw.println("    }");
                        pw.println("}");
                        pw.flush();
                    } finally {
                        w.close();
                    }
                } catch (IOException e1) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            e1.toString());
                }
            }
        }
        return true;
    }
}

步骤2:配置一个spi,在resources目录新建META-INF/services/javax.annotation.processing.Processor,内容为MyProcessor类全名。

步骤3:在另一个项目中使用@ApiAnnotation就会发现生成了一个新My_feignAutoGenerate.class文件:

public class My_feignAutoGenerate {
    public My_feignAutoGenerate() {
    }
    public static void printStartUp() {
        System.out.println("代码生成的路径: file:/C:/Users/Administrator/Desktop/feign-async-master/target/generated-sources/annotations/My_feignAutoGenerate.java");
        System.out.println("注解的元素: com.github.feign.StartUp");
        System.out.println("注解的版本: 1");
        System.out.println("注解的作者: alex");
        System.out.println("注解的日期: 2019-03-6");
    }
}

到这里基本上已经演示完了。

google的 auto-service

<dependency>
    <groupId>com.google.auto.service</groupId>
    <artifactId>auto-service</artifactId>
    <version>1.0-rc2</version>
</dependency>

这个类库非常有用,它非常简单,使用@AutoService(Processor.class)会基于该接口和注解的类上自动帮我们生成META-INF/services下对应spi文件。它实现的原理就是通过注解处理器。

javapoet

有没有觉得上面pw.println("package " + generatePackageName + ";");这样的代码很痛苦啊?

JavaPoetis a Java API for generating .java source files.

package com.example.helloworld;
public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}
MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

你喜欢的lombok实现原理是怎样的呢?

lombok(用来帮助开发人员消除 Java 对象 的冗长),非常好用

里面源码就不再介绍了!!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java并发系列之AbstractQueuedSynchronizer源码分析(共享模式)

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快的理解其他的方式.虽然说AbstractQueuedSynchronizer源码有一千多行,但是重复的也比较多,所以读者不要刚开始的时候被吓到,只要耐着性子去看慢慢的自然能够渐渐领悟.就我个人经验来说,阅读AbstractQueuedSynchronizer源码有几个比较关键的地方需要弄明白,分别是

  • JAVA和JAVAC 命令详细介绍

    JAVA和JAVAC 命令 javac和java命令行中的-classpath选项 这是个很基础的问题,但是因为基本上都是用现有的IDE工具 来开发java程序,所以很少有人意识到这一点. javac -classpath,设定要搜索类的路径,可以是目录,jar文件,zip文件(里面都是class文件),会覆盖掉所有在CLASSPATH里面的设定. -sourcepath, 设定要搜索编译所需java 文件的路径,可以是目录,jar文件,zip文件(里面都是java文件). 所以一个完整的jav

  • java编程abstract类和方法详解

    抽象类和抽象方法常用知识点: (1)抽象类作为被继承类,子类必须实现抽象类中的所有抽象方法,除非子类也为抽象类. 也就是说,如果子类也为抽象类,可以不实现父类中的抽象方法.但是,如果有一个非抽象类 继承于抽象子类,需要实现抽象子类,抽象子类的抽象父类的所有抽象方法,新帐旧账一起算. (2)抽象类不能用final进行修饰. (3)抽象类不能被实例化,也就是说你用的时候不能通过new关键字创建. (4)抽象类中可以包含抽象方法和非抽象方法,抽象方法没有方法体,也就是没有具体实现, 只是定义了有什么功

  • Java并发系列之AbstractQueuedSynchronizer源码分析(独占模式)

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概念,主要讲了AQS的排队区是怎样实现的,什么是独占模式和共享模式以及如何理解结点的等待状态.理解并掌握这些内容是后续阅读AQS源码的关键,所以建议读者先看完我的上一篇文章再回过头来看这篇就比较容易理解.在本篇中会介绍在独占模式下结点是怎样进入同步队列排队的,以及离开同步队列之前会进行哪些操作.AQS为在独占模

  • java冷知识:javac AbstractProcessor详解

    目录 它可以做什么? Processor AbstractProcessor 源码 google的 auto-service javapoet 你喜欢的lombok实现原理是怎样的呢? 它可以做什么? 它做的事情当然是生成新类或修改原始的类,比如你遇到这样的情况下就可以使用: 反射好慢,曾见过一个大厂大量是Gson,由于Gson序列化时大量使用了反射,每一个field,每一个get.set都需要用反射,由此带来了性能问题.解决方法就是使用它尽量减少反射(替换成JSONObject) 生成代码,只

  • Java开发必备知识之数组详解

    一.ASCII码 二.为什么需要数组 案例: 160班 现在 77人 统计 全班的Java成绩 用程序进行存储 变量 统计 全班不及格的同学 要 补考 补考过的同学 修改成绩 定义 77 个变量 int 帅 = 59: int 洋 = 100: int cto = 60: int ceo = 58: 三.什么是数组 概念:数组就是内存中一块 连续的 内存空间,用于存放 相同类型 的多个数据 四.定义数组 声明一个数组:确定数组中存放的数据类型 语法: 数据类型[] 数组名://建议 数据类型 [

  • JVM内存管理之JAVA语言的内存管理详解

    引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓狂的内存溢出和泄露的问题. 可怕的事情还不只如此,有些使用其它语言开发的程序员,给JAVA程序员扣上了一个"不懂内存"的帽子,这着实有点让人难以接受.毕竟JAVA当中没有malloc和delete.没有析构函数.没有指针,刚开始接触JAVA的程序员们又怎么可能接触内存这一部分呢,更何况有不

  • Java String 拼接字符串原理详解

    首先来一道思考题: String str1 = "111111"; String str2 = "222222"; String str = str1 + str2; System.out.println(str); 很明确,上述代码输出的结果是:"111111222222",但是它工作原理是怎样的呢? 由于字符串拼接太常用了,java才支持可以直接用+号对两个字符串进行拼接.**其真正实现的原理是中间通过建立临时的StringBuilder对象

  • Java Scoket实现双向通信代码详解

    你好我是辰兮,很高兴你能来阅读,本篇总结了Java Scoket类的相关知识,并且整理了实现双向通信的相关代码也有案例实现截图,分享获取新知,大家一起进步. 一.网络通信 网络通信,在网络中程序(发送者)与程序(接受者)之间的数据交互. 通信要素①ip + 端口号 ②传输协议 java.net包: 包含了Java用于网络通信所需的类. ServerSocket类,用于表示网络服务 创建网络服务(创建ServerSocket对象) //构造器 public ServerSocket(int por

  • Java基础之容器Vector详解

    一.前言 知识补充:Arrays.copyOf函数: public static int[] copyOf(int[] original, int newLength) { int[] copy = new int[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } 可见copyOf()在内部新建一个数组,调用arrayCopy()将ori

  • Java之Buffer属性案例详解

    一.前言 熟悉NIO的人想必一定不会陌生buffer中position,limit,capacity这三个属性吧,之前在学习的时候遇到一个问题:就是当你先往缓冲区写入一部分数据,然后调用flip()方法,再全部读取完数据,然后再调用flip()方法,此时这三个值的变化是怎样的,研究了一下,决定写下来分享一下. 二.正文 1.介绍 position: 它指的是下一次读取或写入的位置. limit: 指定还有多少数据需要写出(在从缓冲区写入通道时),或者还有多少空间可以读入数据(在从通道读入缓冲区时

  • Java之JSF框架案例详解

    这是一个分为两部分的系列,其中我介绍了JSF 2及其如何适合Java EE生态系统. 在第1部分中,我将介绍JavaServer Pages(JSF)背后的基本思想 ,在第2部分中,将介绍Facelets声明语言 . 在构建Web应用程序时,我们为最终用户提供了一种与我们的应用程序进行交互的方式,这就是JSF所提供的. 我将向您介绍MVC设计模式以及如何使用它,并且您将发现Facelets视图语言及其使用方式,如何将数据和事件绑定到上下文以及如何通过表达语言来实现. 我将通过查看替代模板框架(例

  • Java源码解析之详解ReentrantLock

    ReentrantLock ReentrantLock是一种可重入的互斥锁,它的行为和作用与关键字synchronized有些类似,在并发场景下可以让多个线程按照一定的顺序访问同一资源.相比synchronized,ReentrantLock多了可扩展的能力,比如我们可以创建一个名为MyReentrantLock的类继承ReentrantLock,并重写部分方法使其更加高效. 当一个线程调用ReentrantLock.lock()方法时,如果ReentrantLock没有被其他线程持有,且不存在

  • Java VisualVM监控远程JVM(详解)

    我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目前有多个监控工具, 相比JProfiler对系统资源尤其是内存的消耗是非常庞大,JDK1.6开始自带的VisualVM就是不错的监控工具. 这个工具就在JAVA_HOME\bin\目录下的jvisualvm.exe, 双击这个文件就能看到一个比较直观的界面 从左边Applications树中可以知道,不光可以监控本地JVM运行情况, 还可以监控远程机器上的JVM运行情况. 本地监控:只要打开某个JAVA程序就会自

随机推荐