编译时注解从入门到精通(一)

是什么

编译时注解是一种只在编译期间生效的注解。比如常见的@Override
@Override的定义如下:

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

  • @Target(ElementType.METHOD) 表示此注解只能用在方法上。
  • @Retention(RetentionPolicy.SOURCE) 表示此注解只存在于源码中。

@Retention(RetentionPolicy.RUNTIME) 表示这是一个运行时注解

为什么

我们为什么要使用编译时注解呢?在什么情况下使用呢?

使用案例

  • 【打印日志】可以用于打印方法的出参、入参和耗时。
  • 【管理缓存】可以缓存方法的返回值,也可以删除方法的缓存。
  • 【方法限流】可以限制方法的访问量和访问频率。
  • 【监控预警】可以用于监控接口的调用。
  • 【防重复提交】可以用于防重复提交。

优点

  • 【比运行时注解快】因为运行时注解需要结合反射,而编译时注解只是消耗编译时性能。
  • 【运用范围广】因为她不依赖任何环境,比如spring环境。她能在main方法中使用。

怎么用

本例以maven项目演示。

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<build>
<plugins>
<!--Compiler Plugin -->
<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>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>sun.jdk</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>

<compilerArgument>-proc:none</compilerArgument>表示不使用默认的处理器(因为我们要使用自定义的处理器)。

注册注解处理器

/src/main/resources/META-INF/services/目录下创建文件javax.annotation.processing.Processor, 内容为:

1
com.github.ofofs.jca.processor.JcaProcessor

JcaProcessor是我们自定义的注解处理器,请注意修改为自己的包名。

注解处理器

顾名思义,它就是用来处理注解的,就像我们在使用运行时注解的时候,也会在切面方法中去处理注解一样。注解定义好了,当然要去处理,不然要她做甚。

JcaProcessor.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.github.ofofs.jca.processor;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;

/**
* JCA编译时注解处理器
*
* @author kangyonggan
* @since 6/22/18
*/
@SupportedAnnotationTypes("com.github.ofofs.jca.annotation.Serial")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class JcaProcessor extends AbstractProcessor {

@Override
public synchronized void init(ProcessingEnvironment env) {
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
// 可以在这里处理自定义编译时注解@Serial
return true;
}

}

  1. SupportedAnnotationTypes, 它的value是一个数组,表明我们可以在一个注解处理器中同时处理多个注解。
  2. 要注意init方法中的env和process方法中的env不是同一种。

实例

我想定义一个@Serial的编译时注解,只作用在类上,它的功能是给这个类实现序列化接口(implements Serializable), 并且生成唯一的serialVersionUID。即:

源码:

1
2
3
4
5
6
7
8
9
10
11
12
package com.github.ofofs.jca.serial;

import com.github.ofofs.jca.annotation.Serial;

/**
* @author kangyonggan
* @since 6/22/18
*/
@Serial
public class SerialTest {

}

编译后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.github.ofofs.jca.serial;

import java.io.Serializable;

public class SerialTest implements Serializable {
private static final Long serialVersionUID = 6893472666383691777L;

public SerialTest() {
}
}

感觉是不是很炫酷?很神奇?很强大?其实编译时注解的强大远远超乎你的想象。本篇文章不会贴出此注解的实现代码。下篇文章我将会一一介绍处理编译时注解的各个技巧。