1、JSR-269概述

  1. 1Java6开始纳入了JSR-269规范:Pluggable Annotation Processing API(插件式注解处理器)。
  2. 2、在JSR 269 之前可以使用注解,但必须借助反射机制,而反射的方法局限性较大,由于必须定义@RetentionRetentionPolicy.RUNTIME,只能在运行期通过反射来获取注解值,使得运行时代码效率降低;在JSR 269 之后我们可以在 Javac的编译期利用注解做这些事情。
  3. 3、[!!!]JSR-269提供一套标准API来处理Annotations,具体来说,我们只需要继承AbstractProcessor类,重写process方法实现自己的注解处理逻辑,并且在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor,在javac编译过程中编译器便会调用我们实现的Annotation Processor,从而使得我们有机会对java编译过程中生产的抽象语法树进行修改。

2、JSR-269简介

2.1、javac编译过程

  1. 1、初始化插入式注解处理器(执行 AbstractProcessor init 方法)
  2. 2、词法分析,语法分析,输入符号表,产生 JCTree
  3. 3、注解处理(执行 AbstractProcessor process 方法 )
  4. 4、数据流处理
  5. 5、解语法糖
  6. 6、字节码生成
  7. ### 在执行完[步骤3]注解处理后,会判断语法树有没有变动,没有就执行[步骤4],有变动则需要回到[步骤2]再次进行记法分析,直到所有插入式注解处理器都不再更改语法树为止。
  8. ### AST是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。

2.2、JSR-269实现自己代码规范检查插件-demo

1、需求背景:mybatis,dao层类中对于每一个方法参数,我们都必须机械式地在每个参数前边加上@Param(“xxx”)注解,这使得方法参数列表变得又丑又长,这是因为javac在编译过程中默认不会保留方法参数名。 2、解决方式: 1.1、SQL语句中不使用实际参数名,而是使用#{param1}, #{param2} @Select({“select * from sys_agent_info where agent_name=#{param1} and status=#{param2}”}) AgentInfo findByNameAndStatus(String name, Integer status); 1.2、利用Java8的一项新的特性——在class文件中保留参数名。只需要在javac编译时加上 -parameters参数,在生成的字节码将保留原始的方法参数名,同时配合使用将mybatis中useActualParamName参数设置为true。 1.3、利用JSR-269我们可以修改在编译过程中生成的抽象语法树,因此我们可以编写自己的Annotation Processor,在编译过程中自动在方法参数前边添加@Param注解。

2.2.1、demo示例

  1. 1、自定义注解类
  2. @Retention(RetentionPolicy.SOURCE)
  3. @Target(ElementType.TYPE)
  4. public @interface UseActualParam {
  5. }
  6. 2、编写注解processor类,继承AbstractProcessor类,实现process方法
  7. @SupportedAnnotationTypes("com.kugou.jsr269.UseActualParam")
  8. public class MybatisParamProcessor extends AbstractProcessor {
  9. /**
  10. * JavacTrees提供了待处理的抽象语法树
  11. * TreeMaker中了一些操作抽象语法树节点的方法
  12. * Names提供了创建标识符的方法
  13. */
  14. private JavacTrees trees;
  15. private TreeMaker treeMaker;
  16. private Names names;
  17. @Override
  18. public SourceVersion getSupportedSourceVersion() {
  19. return SourceVersion.RELEASE_8;
  20. }
  21. @Override
  22. public synchronized void init(ProcessingEnvironment processingEnv) {
  23. super.init(processingEnv);
  24. this.trees = JavacTrees.instance(processingEnv);
  25. Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
  26. this.treeMaker = TreeMaker.instance(context);
  27. this.names = Names.instance(context);
  28. }
  29. @Override
  30. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  31. roundEnv.getElementsAnnotatedWith(UseActualParam.class).stream()
  32. .map(element -> trees.getTree(element))
  33. .forEach(tree -> tree.accept(new TreeTranslator() {
  34. @Override
  35. public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
  36. prependParamAnnotation(jcClassDecl);
  37. super.visitClassDef(jcClassDecl);
  38. }
  39. }));
  40. return true;
  41. }
  42. /**
  43. * 在DAO方法参数前边追加@Param注解
  44. */
  45. private void prependParamAnnotation(JCTree.JCClassDecl jcClassDecl) {
  46. jcClassDecl.defs.stream()
  47. .filter(element -> element.getKind().equals(Tree.Kind.METHOD))
  48. .map(methodTree -> (JCTree.JCMethodDecl) methodTree)
  49. .forEach(methodTree -> {
  50. methodTree.getParameters().forEach(parameter -> {
  51. JCTree.JCAnnotation paramAnnotation = createParamAnnotation(parameter);
  52. parameter.getModifiers().annotations.append(paramAnnotation);
  53. });
  54. });
  55. }
  56. /**
  57. * 创建@Param注解对应的语法树对象
  58. */
  59. private JCTree.JCAnnotation createParamAnnotation(JCTree.JCVariableDecl parameter) {
  60. return treeMaker.Annotation(
  61. treeMaker.Ident(names.fromString("Param")),
  62. List.of(treeMaker.Assign(treeMaker.Ident(names.fromString("value")), treeMaker.Literal(parameter.name.toString()))));
  63. }
  64. }
  65. 3、在resources/META-INF/services创建名为javax.annotation.processing.Processor的文本文件,内容填写自己实现的Annotation Processor的全限定类名。
  66. 3.1、比如:lombok.launch.AnnotationProcessorHider$XxxProcessor
  67. 4、编译项目并deploy jar包到maven仓库,在其他项目引入该jar包,在项目编译时即可自动帮我们在DAO方法参数前边生成@Param注解。