• Java编译时注解学习,并简单实现Lombok


         编译时注解可以用来动态生成代码. 使用 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

    实现一个简单的lombok主要分为两个步骤,第一是实现Processor接口处理注解,第二是注册注解处理器。

    1.创建我们自定义的注解

    1. @Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
    2. @Target(ElementType.TYPE) // 用于修饰类
    3. public @interface MySetterGetter {
    4. }

    2.实现Processor接口

    通过实现Processor接口可以自定义注解处理器,这里我们采用更简单的方法通过继承AbstractProcessor类实现自定义注解处理器。实现抽象方法process处理我们想要的功能。

    首先pom文件先引入依赖(jdk文件夹的tools.jar工具类),实现注解时会使用到tools工具类里面的一些方法

    1. com.sun
    2. tools
    3. 1.8
    4. system
    5. D:/tool/jdk1.8.0_111/lib/tools.jar

    实现接口,需要先引入tools.jar工具包。 

    1. @SupportedAnnotationTypes(value = {"com.example.zcs.annotation.lombokTest.MySetterGetter"})
    2. @SupportedSourceVersion(SourceVersion.RELEASE_8)
    3. public class CustomProcessor extends AbstractProcessor {
    4. /**
    5. * AST
    6. */
    7. private JavacTrees trees;
    8. /**
    9. * 操作修改AST
    10. */
    11. private TreeMaker treeMaker;
    12. /**
    13. * 符号封装类,处理名称
    14. */
    15. private Names names;
    16. /**
    17. * 打印信息
    18. */
    19. private Messager messager;
    20. @Override
    21. public synchronized void init(ProcessingEnvironment processingEnv) {
    22. super.init(processingEnv);
    23. trees = JavacTrees.instance(processingEnv);
    24. final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
    25. treeMaker = TreeMaker.instance(context);
    26. names = Names.instance(context);
    27. messager = processingEnv.getMessager();
    28. }
    29. @Override
    30. public boolean process(Set annotations, RoundEnvironment roundEnv) {
    31. System.out.println("begin");
    32. System.out.println("start annotation process .999999999999999999999999");
    33. final Setextends Element> dataAnnotations = roundEnv.getElementsAnnotatedWith(com.example.zcs.annotation.lombokTest.MySetterGetter.class);
    34. dataAnnotations.stream().map(element -> trees.getTree(element)).forEach(
    35. tree -> tree.accept(new TreeTranslator() {
    36. @Override
    37. public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
    38. // print method name
    39. System.out.println("-------------2");
    40. messager.printMessage(Diagnostic.Kind.NOTE, jcMethodDecl.toString());
    41. super.visitMethodDef(jcMethodDecl);
    42. }
    43. @Override
    44. public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
    45. System.out.println("-------------1");
    46. final Map treeMap =
    47. jcClassDecl.defs.stream().filter(k -> k.getKind().equals(Tree.Kind.VARIABLE))
    48. .map(tree -> (JCTree.JCVariableDecl) tree)
    49. .collect(Collectors.toMap(JCTree.JCVariableDecl::getName, Function.identity()));
    50. treeMap.forEach((k, var) -> {
    51. messager.printMessage(Diagnostic.Kind.NOTE, "var:" + k);
    52. System.out.println("-------------3");
    53. try {
    54. // add getter
    55. jcClassDecl.defs = jcClassDecl.defs.prepend(getter(var));
    56. // add setter
    57. jcClassDecl.defs = jcClassDecl.defs.prepend(setter(var));
    58. // jcClassDecl.defs.prepend(setter(var));
    59. } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
    60. e.printStackTrace();
    61. messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
    62. }
    63. });
    64. super.visitClassDef(jcClassDecl);
    65. }
    66. })
    67. );
    68. return true;
    69. }
    70. /**
    71. * 自定义setter
    72. */
    73. private JCTree setter(JCTree.JCVariableDecl var) throws ClassNotFoundException, IllegalAccessException,
    74. InstantiationException {
    75. // 方法级别public
    76. final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
    77. final Name varName = var.getName();
    78. Name methodName = methodName(varName, "set");
    79. // 方法体
    80. ListBuffer jcStatements = new ListBuffer<>();
    81. jcStatements.append(treeMaker.Exec(treeMaker.Assign(
    82. treeMaker.Select(treeMaker.Ident(names.fromString("this")), varName),
    83. treeMaker.Ident(varName)
    84. )));
    85. final JCTree.JCBlock block = treeMaker.Block(0, jcStatements.toList());
    86. // 返回值类型void
    87. JCTree.JCExpression returnType =
    88. treeMaker.Type((Type) (Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance()));
    89. List typeParameters = List.nil();
    90. // 参数
    91. final JCTree.JCVariableDecl paramVars = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER,
    92. List.nil()), var.name, var.vartype, null);
    93. final List params = List.of(paramVars);
    94. List throwClauses = List.nil();
    95. // 重新构造一个方法, 最后一个参数是方法注解的默认值,这里没有
    96. return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
    97. null);
    98. }
    99. /**
    100. * 构造驼峰命名
    101. */
    102. private Name methodName(Name varName, String prefix) {
    103. return names.fromString(prefix + varName.toString().substring(0, 1).toUpperCase()
    104. + varName.toString().substring(1));
    105. }
    106. /**
    107. * 构造getter
    108. */
    109. private JCTree getter(JCTree.JCVariableDecl var) {
    110. // 方法级别
    111. final JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
    112. // 方法名称
    113. final Name methodName = methodName(var.getName(), "get");
    114. // 方法内容
    115. ListBuffer statements = new ListBuffer<>();
    116. statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), var.getName())));
    117. final JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
    118. // 返回值类型
    119. final JCTree.JCExpression returnType = var.vartype;
    120. // 没有参数类型
    121. List typeParameters = List.nil();
    122. // 没有参数变量
    123. List params = List.nil();
    124. // 没有异常
    125. List throwClauses = List.nil();
    126. // 构造getter
    127. return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
    128. null);
    129. }
    130. }

    3.注册注解处理器

    方式一:

           我们还需要将我们自定义的注解处理器进行注册。新建res文件夹,目录下新建META-INF文件夹,目录下新建services文件夹,目录下新建javax.annotation.processing.Processor文件,然后将我们自定义注解处理器的全类名写到此文件:

    方式二:

    上面这种注册的方式有点麻烦,谷歌帮我们写了一个注解处理器来生成这个文件,通过引入依赖的方法。

    1. com.google.auto.service
    2. auto-service
    3. 1.0-rc4
    4. true
    5. com.google.auto
    6. auto-common
    7. 0.10
    8. true

    然后添加注解(这种使用注解进行注册的方式我没有使用过,有兴趣的同学可以使用)

    1. @AutoService(Processor.class)
    2. public class CustomProcessor extends AbstractProcessor {
    3. ...

     4.在pom文件里还要做一个编译插件的配置,不然会编译失败。

    1. org.apache.maven.plugins
    2. maven-compiler-plugin
    3. 3.5.1
    4. -proc:none
    5. 1.8
    6. 1.8

    没有这个配置编译时会报错:提示服务配置文件不正确, 或构造处理程序对象j



    几个配置参数的意思:

    -proc:{none,only}、-procpath、-processor

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

    • -processor :自定义注释处理器的类
    • -procpath:注释处理器的查找目录。
    • -proc:only:只运行注释处理器,而不编译源文件。
    • -proc:none:不使用注释处理器,只编译源文件。

    5.代码写完,可以进行编译了,打成jar包引入到别的项目就可以使用了。

     使用mvn clean package 命令打了jar包。

    将jar包重命名,并放到一个临时目录,方便引入

     6.在别的项目中引入jar包,并对注解进行使用。

    首先在pom文件引入jar包,注意jar包目录要改成自己的

    1. com.zcs
    2. lombok
    3. 1.8
    4. system
    5. C:/Users/zhangchangsi/Desktop/lombok/zcs-lombok.jar

    使用我们的注解声明在类上面。 

    1. @MySetterGetter
    2. public class UserInfo {
    3. private String name;
    4. private int age;
    5. private boolean flag;
    6. public static void main(String[] args) {
    7. UserInfo userInfo = new UserInfo();
    8. userInfo.setName("zcs");
    9. System.out.println(userInfo.getName());
    10. System.out.println(userInfo);
    11. }
    12. @Override
    13. public String toString() {
    14. return "UserInfo{" +
    15. "name='" + name + '\'' +
    16. ", age=" + age +
    17. ", flag=" + flag +
    18. '}';
    19. }
    20. }

    运行main方法可以正常运行:

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

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

  • 相关阅读:
    2023NOIP A层联测16 数学题
    2D物理引擎 Box2D for javascript Games 第六章 关节和马达
    第五章TCP/IP 网络在我们身边
    位运算合集
    MySQL--MHA高可用方案
    openpyxl单元格公式批注字体对齐边框填充
    电子电气架构 --- 关于DoIP的一些闲思 下
    Springboot毕设项目购物网站3ztkv(java+VUE+Mybatis+Maven+Mysql)
    Mysql 5.7 创建索引官方解读
    【逗老师的无线电】MOTOTRBO CPS导入DMR ID通信录的骚操作
  • 原文地址:https://blog.csdn.net/weixin_38860401/article/details/126593388