编译时注解可以用来动态生成代码. 使用 SOURCE 类型注解的代码会在编译时被解析, 生成新的 java 文件, 然后和原来的 java 文件一起编译成字节码. 由于不使用反射功能, 编译时注解不会拖累性能, 因而被许多框架使用, 比如 Butter Knife, Dragger2 等.
一些基本概念
在开始之前,我们需要声明一件重要的事情是:我们不是在讨论在运行时通过反射机制运行处理的注解,而是在讨论在编译时处理的注解。
编译时注解跟运行时注解到底区别在什么地方?其实说大也不大,主要是考虑到性能上面的问题。运行时注解主要是完全依赖于反射,反射的效率比原生的慢,所以在内存比较少,CPU比较烂的机器上会有一些卡顿现象出现。而编译时注解完全不会有这个问题,因为它在我们编译过程(java->class)中,通过一些注解标示,去动态生成一些类或者文件,所以跟我们的APK运行完全没有任何关系,自然就不存在性能上的问题。所以一般比较著名的开源项目如果采用注解功能,通常采用编译时注解
注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。这里,我假设你已经了解什么是注解及如何自定义注解。如果你还未了解注解的话,可以查看官方文档。注解处理器在 Java 5 的时候就已经存在了,但直到 Java 6 (发布于2006看十二月)的时候才有可用的API。过了一段时间java的使用者们才意识到注解处理器的强大。所以最近几年它才开始流行。
一个特定注解的处理器以 java 源代码(或者已编译的字节码)作为输入,然后生成一些文件(通常是.java文件)作为输出。那意味着什么呢?你可以生成 java 代码!这些 java 代码在生成的.java文件中。因此你不能改变已经存在的java类,例如添加一个方法。这些生成的 java 文件跟其他手动编写的 java 源代码一样,将会被 javac 编译。
Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。
实现一个简单的lombok主要分为两个步骤,第一是实现Processor接口处理注解,第二是注册注解处理器。
- @Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
- @Target(ElementType.TYPE) // 用于修饰类
- public @interface MySetterGetter {
-
- }
通过实现Processor接口可以自定义注解处理器,这里我们采用更简单的方法通过继承AbstractProcessor类实现自定义注解处理器。实现抽象方法process处理我们想要的功能。
首先pom文件先引入依赖(jdk文件夹的tools.jar工具类),实现注解时会使用到tools工具类里面的一些方法
-
com.sun -
tools -
1.8 -
system -
D:/tool/jdk1.8.0_111/lib/tools.jar -
实现接口,需要先引入tools.jar工具包。
- @SupportedAnnotationTypes(value = {"com.example.zcs.annotation.lombokTest.MySetterGetter"})
- @SupportedSourceVersion(SourceVersion.RELEASE_8)
- public class CustomProcessor extends AbstractProcessor {
- /**
- * AST
- */
- private JavacTrees trees;
- /**
- * 操作修改AST
- */
- private TreeMaker treeMaker;
- /**
- * 符号封装类,处理名称
- */
- private Names names;
- /**
- * 打印信息
- */
- private Messager messager;
-
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- trees = JavacTrees.instance(processingEnv);
- final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
- treeMaker = TreeMaker.instance(context);
- names = Names.instance(context);
- messager = processingEnv.getMessager();
- }
-
- @Override
- public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- System.out.println("begin");
- System.out.println("start annotation process .999999999999999999999999");
-
- final Set extends Element> dataAnnotations = roundEnv.getElementsAnnotatedWith(com.example.zcs.annotation.lombokTest.MySetterGetter.class);
-
- dataAnnotations.stream().map(element -> trees.getTree(element)).forEach(
- tree -> tree.accept(new TreeTranslator() {
- @Override
- public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
- // print method name
- System.out.println("-------------2");
- messager.printMessage(Diagnostic.Kind.NOTE, jcMethodDecl.toString());
- super.visitMethodDef(jcMethodDecl);
- }
-
- @Override
- public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
-
- System.out.println("-------------1");
- final Map
treeMap = - jcClassDecl.defs.stream().filter(k -> k.getKind().equals(Tree.Kind.VARIABLE))
- .map(tree -> (JCTree.JCVariableDecl) tree)
- .collect(Collectors.toMap(JCTree.JCVariableDecl::getName, Function.identity()));
-
- treeMap.forEach((k, var) -> {
- messager.printMessage(Diagnostic.Kind.NOTE, "var:" + k);
- System.out.println("-------------3");
- try {
- // add getter
- jcClassDecl.defs = jcClassDecl.defs.prepend(getter(var));
- // add setter
- jcClassDecl.defs = jcClassDecl.defs.prepend(setter(var));
- // jcClassDecl.defs.prepend(setter(var));
- } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
- e.printStackTrace();
- messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
- }
- });
-
- super.visitClassDef(jcClassDecl);
- }
- })
- );
- return true;
- }
-
- /**
- * 自定义setter
- */
- private JCTree setter(JCTree.JCVariableDecl var) throws ClassNotFoundException, IllegalAccessException,
- InstantiationException {
- // 方法级别public
- final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
-
- final Name varName = var.getName();
- Name methodName = methodName(varName, "set");
-
- // 方法体
- ListBuffer
jcStatements = new ListBuffer<>(); - jcStatements.append(treeMaker.Exec(treeMaker.Assign(
- treeMaker.Select(treeMaker.Ident(names.fromString("this")), varName),
- treeMaker.Ident(varName)
- )));
- final JCTree.JCBlock block = treeMaker.Block(0, jcStatements.toList());
-
- // 返回值类型void
- JCTree.JCExpression returnType =
- treeMaker.Type((Type) (Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance()));
-
- List
typeParameters = List.nil(); -
- // 参数
- final JCTree.JCVariableDecl paramVars = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER,
- List.nil()), var.name, var.vartype, null);
- final List
params = List.of(paramVars); -
- List
throwClauses = List.nil(); - // 重新构造一个方法, 最后一个参数是方法注解的默认值,这里没有
- return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
- null);
- }
-
- /**
- * 构造驼峰命名
- */
- private Name methodName(Name varName, String prefix) {
- return names.fromString(prefix + varName.toString().substring(0, 1).toUpperCase()
- + varName.toString().substring(1));
- }
-
- /**
- * 构造getter
- */
- private JCTree getter(JCTree.JCVariableDecl var) {
- // 方法级别
- final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
-
- // 方法名称
- final Name methodName = methodName(var.getName(), "get");
-
- // 方法内容
- ListBuffer
statements = new ListBuffer<>(); - statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), var.getName())));
- final JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
-
- // 返回值类型
- final JCTree.JCExpression returnType = var.vartype;
-
- // 没有参数类型
- List
typeParameters = List.nil(); -
- // 没有参数变量
- List
params = List.nil(); -
- // 没有异常
- List
throwClauses = List.nil(); -
- // 构造getter
- return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
- null);
- }
- }
方式一:
我们还需要将我们自定义的注解处理器进行注册。新建res文件夹,目录下新建META-INF文件夹,目录下新建services文件夹,目录下新建javax.annotation.processing.Processor文件,然后将我们自定义注解处理器的全类名写到此文件:

方式二:
上面这种注册的方式有点麻烦,谷歌帮我们写了一个注解处理器来生成这个文件,通过引入依赖的方法。
-
-
com.google.auto.service -
auto-service -
1.0-rc4 -
true -
-
-
-
com.google.auto -
auto-common -
0.10 -
true -
然后添加注解(这种使用注解进行注册的方式我没有使用过,有兴趣的同学可以使用)
- @AutoService(Processor.class)
- public class CustomProcessor extends AbstractProcessor {
- ...
-
-
-
-
org.apache.maven.plugins -
maven-compiler-plugin -
3.5.1 -
-
- -proc:none
-
-
1.8 -
1.8 -
-
-
-
没有这个配置编译时会报错:提示服务配置文件不正确, 或构造处理程序对象j

几个配置参数的意思:
这三个命令是用来自定义“Annotation Processor”的,即你可以自定义注释,比如@Hello,@First 等,解析这些注释就需要"Annotation Processor"。

使用mvn clean package 命令打了jar包。
将jar包重命名,并放到一个临时目录,方便引入

首先在pom文件引入jar包,注意jar包目录要改成自己的
-
com.zcs -
lombok -
1.8 -
system -
C:/Users/zhangchangsi/Desktop/lombok/zcs-lombok.jar -
使用我们的注解声明在类上面。

- @MySetterGetter
- public class UserInfo {
- private String name;
- private int age;
- private boolean flag;
-
- public static void main(String[] args) {
- UserInfo userInfo = new UserInfo();
- userInfo.setName("zcs");
- System.out.println(userInfo.getName());
- System.out.println(userInfo);
- }
-
- @Override
- public String toString() {
- return "UserInfo{" +
- "name='" + name + '\'' +
- ", age=" + age +
- ", flag=" + flag +
- '}';
- }
- }
运行main方法可以正常运行:

编译后的java类也能正常生成set/get方法

到此就说明我们自己定义的编译时注解已经可以实现自动生成get/set方法了。