前言
简介
Lombok是一款好用顺手的工具,就像Google Guava一样,在此予以强烈推荐,每一个Java工程师都应该使用它。Lombok是一种Java™实用工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现Lombok,开发人员可以节省构建诸如hashCode()和equals()这样的方法以及以往用来分类各种accessor和mutator的大量时间。
Lombok的实现方式是什么呢?
新建一个测试类使用Lombok的Getter和Setter注解,通过IDEA进行编译
import lombok.Getter; import lombok.Setter; @Getter @Setter public class UserInfo { private String userId; private String userName; }
打开编译后生成的UserInfo.class文件
发现已经生成了get、set方法,由此可以推断出Lombok是在编译期为代码进行了增强,那么在编译期进行增强是如何实现的?
编译阶段
在JDK6提出并通过了JSR-269提案,提案通过了一组被称为“插入式注解处理器”的标准API,可以提前至编译期对代码中的特定注解进行处理, 从而影响到编译器的工作过程。
对于底层的一些实现,普遍会认为实现是像虚拟机一样使用C++实现,对于Java程序员来说并不是特别友好。但是Javac编译器是使用Java实现的更容易上手。
Javac的编译过程大致分为几步
- 准备过程:初始化插入式注解处理器
- 解析与填充符号表过程 词法、语法分析构建抽象语法树(AST)
- 插入式注解处理器的注解处理过程
- 分析与字节码生成过程
- 语法树变动后会再次解析与填充符号表,语法树没有变动时编译器就不会再对源码字符流操作,而是基于抽象语法树
综上所述想实现Lombok的效果只需要遵守JSR-269在编译期对AST进行操作即可实现
当然不止有Lombok通过这种方式实现,例如FindBug、MapStruct等也通过这种方式实现
实现
JCTree
JCTree是AST元素的基类,想实现效果只需要添加JCTree节点即可
JCTree是一个抽象类部分实现
从类名可以猜到是什么节点使用的这里挑几个常用的解释
JCStatement 声明语法树节点
JCBlock 语法块
JCReturn:return语句语法树节点
JCClassDecl:类定义语法树节点
JCVariableDecl:字段/变量定义语法树节点
JCMethodDecl:方法定义语法树节点
JCModifiers:访问标志语法树节点
JCExpression:表达式语法树节点,常见的子类如下
JCAssign:赋值语句语法树节点
JCIdent:标识符语法树节点 例如this
TreeMaker
主要用于生成语法树节点
代码
首先需要注解类,标明作用的范围和作用的时期,两个类分别对应Lombok的Getter、Setter
@Target({ElementType.TYPE}) //加在类上的注解 @Retention(RetentionPolicy.SOURCE) //作用于编译期 public @interface Getter { } @Target({ElementType.TYPE}) //加在类上的注解 @Retention(RetentionPolicy.SOURCE) //作用于编译期 public @interface Setter { }
新建抽象注解处理器需要继承AbstractProcessor,这里使用模板方法模式
public abstract class MyAbstractProcessor extends AbstractProcessor { //语法树 protected JavacTrees trees; //构建语法树节点 protected TreeMaker treeMaker; //创建标识符的对象 protected Names names; @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) { //获取注解标识的类 Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation()); //拿到语法树 set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() { //拿到类定义 @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); //拿到所有成员变量 for (JCTree tree : jcClassDecl.defs) { if (tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } jcVariableDeclList.forEach(jcVariableDecl -> { jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } })); return true; } /** * 创建方法 * @param jcVariableDecl * @return */ public abstract JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl); /** * 获取何种注解 * @return */ public abstract Class<? extends Annotation> getAnnotation(); }
用于处理Setter注解 继承MyAbstractProcessor
@SupportedAnnotationTypes("com.ingxx.processor.Setter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion public class SetterProcessor extends MyAbstractProcessor { @Override public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); //生成函数体 this.name = name; statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName())))); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); //生成方法 return treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), //访问标志 getNewMethodName(jcVariableDecl.getName()), //名字 treeMaker.TypeIdent(TypeTag.VOID), //返回类型 List.nil(), //泛型形参列表 List.of(getParameters(jcVariableDecl)), //参数列表 List.nil(), //异常列表 body, //方法体 null //默认方法(可能是interface中的那个default) ); } @Override public Class<? extends Annotation> getAnnotation() { return Setter.class; } private Name getNewMethodName(Name name) { String fieldName = name.toString(); return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length())); } private JCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) { return treeMaker.VarDef( treeMaker.Modifiers(Flags.PARAMETER), //访问标志 prototypeJCVariable.name, //名字 prototypeJCVariable.vartype, //类型 null //初始化语句 ); } }
用于处理Getter注解 继承MyAbstractProcessor
@SupportedAnnotationTypes("com.ingxx.processor.Getter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion public class GetterProcessor extends MyAbstractProcessor { @Override public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); //生成函数体 return this.字段名 statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()))); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); //生成方法 return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null); } @Override public Class<? extends Annotation> getAnnotation() { return Getter.class; } private Name getNewMethodName(Name name) { String fieldName = name.toString(); return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length())); } }
编译现有类
javac -cp $JAVA_HOME/lib/tools.jar *.java -d .
新建一个测试类 IDEA中由于找不到get set方法会报错,可以忽略
@Getter @Setter public class UserInfo { private String userId; private String userName; public static void main(String[] args) { UserInfo userInfo = new UserInfo(); userInfo.setUserId("001"); userInfo.setUserName("得物"); System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName()); } }
接着编译
//多个处理器用逗号分隔 javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .
查看编译后的文件发现已经生成了get、set方法
以上就是从Lombok到JSR-269的详细内容,更多关于JS 反射机制的资料请关注自学编程网其它相关文章!
- 本文固定链接: https://zxbcw.cn/post/214008/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)