java基础之编译器注解

java基础之编译器注解

上一篇我们讲到了如何实现编译器的注解,本期让我们来看一下源文件注解的创建和使用.

在JAVA1.6后,以后不再默认包含APT了,并且相关的资源都被移除了比如Mirror相关类.提供了新的插件化注解处理API(Pluggable Annotation Processing API).

毕竟现在大家都是1.8或以上版本(嗯 是时候搞个jdk11了),本文就介绍新的插件化注解处理API(原理差不多),若对原有的APT处理有兴趣的小伙伴可以参照java编程思想注解部分学习(这么经典的书,不可能没有是不是,真没有?小哈提供了电子版下载地址关注公众号私聊小哈获取)

什么是插件化注解处理API

插件化注解处理(Pluggable Annotation Processing)APIJSR 269提供一套标准API来处理AnnotationsJSR 175.实际上JSR 269不仅仅用来处理Annotation,我觉得更强大的功能是它建立了Java 语言本身的一个模型,它把method、package、constructor、type、variable、enum、annotation等Java语言元素映射为Types和Elements,从而将Java语言的语义映射成为对象,我们可以在javax.lang.model包下面可以看到这些类。所以我们可以利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境。JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation(源文件注解), Annotation Processor相当于编译器的一个插件,所以称为插入式注解处理.如果Annotation Processor处理Annotation时(执行process()方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,如果第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止(可以理解为递归调用到无可调用)。每执行一次process()方法被称为一个round,这样整个Annotation processing过程可以看作是一个round的序列。JSR 269主要被设计成为针对Tools或者容器的API。这个特性虽然在JavaSE 6已经存在,但是很少人知道它的存在.(lombok就是使用这个特性实现编译期的代码插入的)

Pluggable Annotation Processing API的核心是Annotation Processor注解处理器,一般需要继承抽象类javax.annotation.processing.AbstractProcessor。注意,与运行时注解RetentionPolicy.RUNTIME不同,注解处理器只会处理编译期注解,也就是RetentionPolicy.SOURCE的注解类型,处理的阶段位于Java代码编译期间

使用步骤

  • 自定义源注解:@Retention(RetentionPolicy.SOURCE)

  • 自定义任务处理器(Annotation Processor):

 继承javax.annotation.processing.AbstractProcessor,并覆写process方法.
 Annotation Processor中使用javax.annotation.processing.SupportedAnnotationTypes指定注解类型的名称(注意需要全类名,"包名.注解类型名称",否则会不生效).
 Annotation Processor中使用javax.annotation.processing.SupportedSourceVersion指定编译版本.
 Annotation Processor中使用javax.annotation.processing.SupportedOptions 指定编译参数 可选参数
  • 使用:在类上添加自定义注解

demo1 基础版

  • 创建注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface SourceTest {
    public String value() default "默认值";
}
  • 定义一个注解处理器

@SupportedAnnotationTypes(value = {"test2.SourceTest"})
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
public class SourceTestAnnotationProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("Log in AnnotationProcessor.process");
        for (TypeElement typeElement : annotations) {
            System.out.println(typeElement);
        }
        System.out.println(roundEnv);
        return true;
    }
}
  • 编写测试主类

public class SourceTestMain {
    public static void main(String[] args) throws Exception {
        System.out.println("开始插入式注解测试");
        test();
    }
    @SourceTest(value = "测试编译器注解")
    public static void test()throws Exception{
    }
}

编译方式

  1. 直接使用编译参数指定,例如:javac -processor test2.SourceTestAnnotationProcessor SourceTestMain.java。

  2. 通过服务注册指定,就是META-INF/services/javax.annotation.processing.Processor文件中添加test2.SourceTestAnnotationProcessor.

  3. 通过Maven的编译插件的配置指定如下:

 <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <annotationProcessors>
                    <annotationProcessor>
                        test2.SourceTestAnnotationProcessor
                    </annotationProcessor>
                </annotationProcessors>
            </configuration>
        </plugin>

注意

  • 使用idea时需要开启注解支持,如图所示

     

  • 使用前注解处理器SourceTestAnnotationProcessor必须先被编译,不然会得到下面的报错信息

运行成功得到结果

Log in AnnotationProcessor.process
[errorRaised=false, rootElements=[club.throwable.processor.Test,club.throwable.processor.Main, club.throwable.processor.AnnotationProcessor, processingOver=false]
Log in AnnotationProcessor.process
[errorRaised=false, rootElements=[], processingOver=false]
Log in AnnotationProcessor.process
[errorRaised=false, rootElements=[], processingOver=true]

进阶版 类lombok注解

设置一个@Builder注解使类可以使用Build模式创建

  • 创建@Builder注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
}
  • 创建实体类Person

public class Person {

    private Integer age;
    private String name;

    public Integer getAge() {
        return age;
    }

    @Builder
    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    @Builder
    public void setName(String name) {
        this.name = name;
    }
}
  • 创建注解处理器BuilderProcessor

@SupportedAnnotationTypes(value = {"club.throwable.processor.builder.Builder"})
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
public class BuilderProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement typeElement : annotations) {
            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(typeElement);
            Map<Boolean, List<Element>> annotatedMethods
                    = annotatedElements.stream().collect(Collectors.partitioningBy(
                    element -> ((ExecutableType) element.asType()).getParameterTypes().size() == 1
                            && element.getSimpleName().toString().startsWith("set")));
            List<Element> setters = annotatedMethods.get(true);
            List<Element> otherMethods = annotatedMethods.get(false);
            otherMethods.forEach(element ->
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            "@Builder must be applied to a setXxx method "
                                    + "with a single argument", element));
            Map<String, String> setterMap = setters.stream().collect(Collectors.toMap(
                    setter -> setter.getSimpleName().toString(),
                    setter -> ((ExecutableType) setter.asType())
                            .getParameterTypes().get(0).toString()
            ));
            String className = ((TypeElement) setters.get(0)
                    .getEnclosingElement()).getQualifiedName().toString();
            try {
                writeBuilderFile(className, setterMap);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    private void writeBuilderFile(
            String className, Map<String, String> setterMap)
            throws IOException {
        String packageName = null;
        int lastDot = className.lastIndexOf('.');
        if (lastDot > 0) {
            packageName = className.substring(0, lastDot);
        }
        String simpleClassName = className.substring(lastDot + 1);
        String builderClassName = className + "Builder";
        String builderSimpleClassName = builderClassName
                .substring(lastDot + 1);

        JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(builderClassName);

        try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {

            if (packageName != null) {
                out.print("package ");
                out.print(packageName);
                out.println(";");
                out.println();
            }
            out.print("public class ");
            out.print(builderSimpleClassName);
            out.println(" {");
            out.println();
            out.print("    private ");
            out.print(simpleClassName);
            out.print(" object = new ");
            out.print(simpleClassName);
            out.println("();");
            out.println();
            out.print("    public ");
            out.print(simpleClassName);
            out.println(" build() {");
            out.println("        return object;");
            out.println("    }");
            out.println();
            setterMap.forEach((methodName, argumentType) -> {
                out.print("    public ");
                out.print(builderSimpleClassName);
                out.print(" ");
                out.print(methodName);

                out.print("(");

                out.print(argumentType);
                out.println(" value) {");
                out.print("        object.");
                out.print(methodName);
                out.println("(value);");
                out.println("        return this;");
                out.println("    }");
                out.println();
            });
            out.println("}");
        }
    }
}
  • 提起编译注解处理器

javac BuilderProcessor.java
  • 编译

META-INF/services/javax.annotation.processing.Processor文件中添加test3.BuilderProcessor

执行mvn compile
  • 结果生成PersonBuilder

public class PersonBuilder {
    private Person object = new Person();

    public PersonBuilder() {
    }

    public Person build() {
        return this.object;
    }

    public PersonBuilder setName(String value) {
        this.object.setName(value);
        return this;
    }

    public PersonBuilder setAge(Integer value) {
        this.object.setAge(value);
        return this;
    }
}

关注订阅号程序猿小哈,联系作者,加入学习交流群和小伙伴们一起学习进

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值