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.创建我们自定义的注解

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

}

2.实现Processor接口

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

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

<dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>D:/tool/jdk1.8.0_111/lib/tools.jar</systemPath>
        </dependency>

实现接口,需要先引入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<Name, JCTree.JCVariableDecl> 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<JCTree.JCStatement> 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<JCTree.JCTypeParameter> typeParameters = List.nil();

        // 参数
        final JCTree.JCVariableDecl paramVars = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER,
                List.nil()), var.name, var.vartype, null);
        final List<JCTree.JCVariableDecl> params = List.of(paramVars);

        List<JCTree.JCExpression> 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<JCTree.JCStatement> 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<JCTree.JCTypeParameter> typeParameters = List.nil();

        // 没有参数变量
        List<JCTree.JCVariableDecl> params = List.nil();

        // 没有异常
        List<JCTree.JCExpression> throwClauses = List.nil();

        // 构造getter
        return treeMaker.MethodDef(modifiers, methodName, returnType, typeParameters, params, throwClauses, block,
                null);
    }
}

3.注册注解处理器

方式一:

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

方式二:

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

        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc4</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.google.auto</groupId>
            <artifactId>auto-common</artifactId>
            <version>0.10</version>
            <optional>true</optional>
        </dependency>

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

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

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

   <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <compilerArgument>
                        -proc:none
                    </compilerArgument>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

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



几个配置参数的意思:

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

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

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

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

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

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

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

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

<dependency>
            <groupId>com.zcs</groupId>
            <artifactId>lombok</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>C:/Users/zhangchangsi/Desktop/lombok/zcs-lombok.jar</systemPath>
        </dependency>

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

@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方法了。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值