1、JSR-269概述
1、Java6开始纳入了JSR-269规范:Pluggable Annotation Processing API(插件式注解处理器)。
2、在JSR 269 之前可以使用注解,但必须借助反射机制,而反射的方法局限性较大,由于必须定义@Retention为RetentionPolicy.RUNTIME,只能在运行期通过反射来获取注解值,使得运行时代码效率降低;在JSR 269 之后我们可以在 Javac的编译期利用注解做这些事情。
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、初始化插入式注解处理器(执行 AbstractProcessor 的 init 方法)
2、词法分析,语法分析,输入符号表,产生 JCTree
3、注解处理(执行 AbstractProcessor 的 process 方法 )
4、数据流处理
5、解语法糖
6、字节码生成
### 在执行完[步骤3]注解处理后,会判断语法树有没有变动,没有就执行[步骤4],有变动则需要回到[步骤2]再次进行记法分析,直到所有插入式注解处理器都不再更改语法树为止。
### 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、自定义注解类
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface UseActualParam {
}
2、编写注解processor类,继承AbstractProcessor类,实现process方法
@SupportedAnnotationTypes("com.kugou.jsr269.UseActualParam")
public class MybatisParamProcessor extends AbstractProcessor {
/**
* JavacTrees提供了待处理的抽象语法树
* TreeMaker中了一些操作抽象语法树节点的方法
* Names提供了创建标识符的方法
*/
private JavacTrees trees;
private TreeMaker treeMaker;
private Names names;
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
roundEnv.getElementsAnnotatedWith(UseActualParam.class).stream()
.map(element -> trees.getTree(element))
.forEach(tree -> tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
prependParamAnnotation(jcClassDecl);
super.visitClassDef(jcClassDecl);
}
}));
return true;
}
/**
* 在DAO方法参数前边追加@Param注解
*/
private void prependParamAnnotation(JCTree.JCClassDecl jcClassDecl) {
jcClassDecl.defs.stream()
.filter(element -> element.getKind().equals(Tree.Kind.METHOD))
.map(methodTree -> (JCTree.JCMethodDecl) methodTree)
.forEach(methodTree -> {
methodTree.getParameters().forEach(parameter -> {
JCTree.JCAnnotation paramAnnotation = createParamAnnotation(parameter);
parameter.getModifiers().annotations.append(paramAnnotation);
});
});
}
/**
* 创建@Param注解对应的语法树对象
*/
private JCTree.JCAnnotation createParamAnnotation(JCTree.JCVariableDecl parameter) {
return treeMaker.Annotation(
treeMaker.Ident(names.fromString("Param")),
List.of(treeMaker.Assign(treeMaker.Ident(names.fromString("value")), treeMaker.Literal(parameter.name.toString()))));
}
}
3、在resources/META-INF/services创建名为javax.annotation.processing.Processor的文本文件,内容填写自己实现的Annotation Processor的全限定类名。
3.1、比如:lombok.launch.AnnotationProcessorHider$XxxProcessor
4、编译项目并deploy jar包到maven仓库,在其他项目引入该jar包,在项目编译时即可自动帮我们在DAO方法参数前边生成@Param注解。