一、什么是注解
注解也叫元数据, 例如我们常见的@Override和@Deprecated等。注解是JDK1.5引入的一个特性, 用于对类、方法、字段、参数、构造器、包、局部变量等进行注解。
一般常用的注解可以分为三类:
1、Java自带的标准注解: 包括@Override(标明重写某个方法)和@Deprecated(标明某个方法被废弃)、@SuppressWarnings(标明要忽略的警告), 使用这些注解后编译器就会进行检查。
2、元注解, 元注解是定义注解的注解:包括@Retention(标明注解的被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可被继承)、@Documented(标明是否生成文档)。
3、自定义注解
二、注解的用途
- 编译检查: 提供信息给编译器,编译器可以利用注解来检测出错误或警告信息, 打印出日志
- 编译时动态处理: 利用注解编译期间动态生成代码或生成文档或其他的自动化处理。
- 运行时动态处理: 运行时通过代码里标示的元数据动态处理, 如通过反射注入实例等。
三、 注解的组成
1、 注解的架构
Annotation架构图如下:
image.png
从上面看出(实际可以自己编译一下注解的class文件),
1)注解都是继承自java.lang.annoation.Annoation接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object var1);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
2)每一个注解由一个或多个ElementType和RetentionPolicy构成(必须声明RetentionPolicy, 否则获取不到注解)
Example:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
//@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnAnnotationType {
String value() default "haha";
}
image.png
可以看到如果定义的注解未声明RetentionPolicy, 被注解的类等是无法通过反射获得Annoation实例的。
经实验,其他的元注解可有可无,如果没定义会使用默认的类型。
3)除过@Target和@Retention, 还有@Docuemented、@Inherited、@Repeatable、@Native多个元注解。
image.png
- 注解不可以被继承
注解是不可以被继承的, 也就是说我们定义的注解不可以通过extends的方式被另外一个注解继承。
2、元注解
元注解顾名思义我们可以理解为注解的注解, 它是作用在注解中,方便我们使用注解实现想要实现的功能。
元注解包括: @Retention、@Target、@Inherited、@Documented、@Repeatable(JDK1.8引入)、@Native(JDK1.8引入)共6个。
2.1、@Target
@Target表示注解修饰的目标, 使用@Target表明我们的注解起作用的范围, 包括类、字段、方法、参数、构造器、包、注解等, 通过java.lang.annoation.ElementType表示:
@Target:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
从Target的定义可以看到, 它的注解目标是注解,它的值是一个ElementType数组。
ElementType:
package java.lang.annotation;
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE,
MODULE;
private ElementType() {
}
}
- ElementType.TYPE: 作用于类、接口、注解、枚举等
- ElementType.FIELD: 作用于属性字段、枚举的属性等
- ElementType.METHOD: 作用于方法
- ElementType.PARAMETER: 作用于参数
- ElementType.CONSTRUCTOR: 作用于构造器
- ElemengType.LOCAL_VARIABLE: 作用于局部变量
- Element Type.ANNOATION_TYPE: 作用于注解
- ElementType.PACKAGE: 作用于包
- ElementType.TYPE_PARAMETER: 作用于类型泛型, 如泛型参数等
2.2、@Retention
@Retention表示注解保留的阶段, 它表示注解是保留在源码阶段(编译器)、编译阶段(类加载)还是运行阶段(类运行), 共有3种含义:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Retention {
RetentionPolicy value();
}
可以看到Retention的值是一个RetentionPolicy类型, RetentionPolicy是一个枚举,共有3种定义:
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME;
private RetentionPolicy() {
}
}
- SOURCE: 注解仅保留在源码中, 编译器会丢弃, 编译后的class字节码文件中不包含。
- CLASS: 注解仅保留在字节码中, 编译器不会丢弃, 编译后的class文件中包含, JVM加载字节码时不会加载。
- RUNTIME: 注解会在class文件中存在, 运行期间可以通过反射获取
2.3、@Inherited
@Inherited的英文意思是继承, 一个被@Inherited注解的注解修饰的父类, 如果它的子类没有被其他注解注释, 则它的子类也继承了父类的注解。
Example:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnAnnotationType {
String value() default "haha";
}
@MyAnAnnotationType("123")
public class Test {
}
public class Test3 extends Test{
public static void main(String[] args) {
MyAnAnnotationType type = Test3.class.getAnnotation(MyAnAnnotationType.class);
System.out.println(type.value());
}
}
输出: 123
2.4、 @Documented
@Documented的英文意思是文档, 它可以将注解中包含的元素包含到javadoc中去。
2.5、@Repeatable
@Repeatable的意思是可重复的, 被@Repeatable修饰的注解可以在同一个地方使用多次,在此之前在同一个地方一个注解只能使用一次。
Repeatable源码如下:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Repeatable {
Class<? extends Annotation> value();
}
@Repeatable的使用要点:
- @Repeatable的参数为被装饰的容器类的class对象。
- 在需要重复使用的注解上修饰@Repeatable。
- 容器包含一个value方法, 返回一个被修饰注解的数组。
Example: 一个Group有多个People, 如果没有@Repeatable注解, 那么我们需要这样定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Peoples {
String[] value();
}
@Peoples({"1", "2"})
public class Group {
public static void main(String[] args) {
Peoples ps = Group.class.getAnnotation(Peoples.class);
System.out.println(ps.value().length);
People[] pss = Group.class.getAnnotationsByType(People.class);
System.out.println(pss.length);
}
}
看起来比较麻烦, 且复杂场景下比较难以实现。
如果有@Repeatable注解, 我们可以这样做:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Peoples.class)
public @interface People {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Peoples {
People[] value();
}
@People("1")
@People("2")
public class Group {
public static void main(String[] args) {
Peoples ps = Group.class.getAnnotation(Peoples.class);
System.out.println(ps.value().length);
People[] pss = Group.class.getAnnotationsByType(People.class);
System.out.println(pss.length);
}
}
看起来会简洁很多, 实际上这只是一个语法糖。可以参考一篇写的比较好的文章: https://juejin.cn/post/6844904142041792525
2.6、 @Native
@Native用来修饰一个字段, 代表引用本地常量。
四、 注解的使用
1、定义注解
1.1、注解的属性
注解的属性和类的变量差不多,只是注解中的属性都是成员变量, 并且注解中没有方法, 只有成员变量。变量名就是注解括号中对应的参数名, 返回值就是注解括号中对应的类型。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
@Target的属性就是value, 是一个java.lang.annoation.ElementType类型。
1.2、 注解的属性类型
注解的属性类型可以是下面的任何一种类型:
- 基本数据类型
- String类型
- 枚举类型
- 注解类型
- Class类型
- 以上几种类型的一维数组类型。
注解的参数应该如何设定:
- 注解的类型必须是以上几种之一。
- 只能用public或默认(default)访问修饰符修饰。
- 如果只有一个参数, 最好使用value来表示。
- 直接的参数可以设置默认值, 方式是参数后面加 default 默认值, 例如: String value default "default";
1.3、注解的声明方式
注解使用@interface声明, 如下 :
public @interface interfaceName{
// 方法体
}
一般来说, 定义注解需要定义@Target和@Retention, 如下:
@Target(作用目标)
@Retention(保留阶段)
public @interface interfaceName{
// 方法体
}
1.4、注解属性的默认值
注解元素必须有确定的值,要么在定义注解的时候定义, 要么在使用注解的时候声明, 非基本类型的注解元素值不可以为null。
声明默认值的方式如下:
public @interface interfaceName{
String value() default "";
int age() default -1;
}
1.5、 注解不可以被继承
注解不能通过extends的方式继承另外一个注解。
1.6、自定义一个注解
@Documented
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotationType {
String business();
long minValue() default 10L;
long maxValue() default 10000L;
}
2、提取注解
注解定义后, 需要在使用的时候提取出来才能产生作用, 注解的提取是通过反射来实现。
注解的提取主要有以下几种方式:
- <A extends Annotation> getAnnotation(Class<A> annotationClass): 通过注解的class获取一个注解实例
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (Annotation)this.annotationData().annotations.get(annotationClass);
}
- Annotation[] getAnnotations() : 获取所有的注解
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(this.annotationData().annotations);
}
- <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass): 通过注解的class获取注解数组
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
Class.AnnotationData annotationData = this.annotationData();
return AnnotationSupport.getAssociatedAnnotations(annotationData.declaredAnnotations, this, annotationClass);
}
同时,还有几个方法,判断是否存在对应的注解对象:
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass): 判断是不是指定注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return super.isAnnotationPresent(annotationClass);
}
- boolean isAnnotation(): 判断是不是注解
public boolean isAnnotation() {
return (this.getModifiers() & 8192) != 0;
}
Example:
@Documented
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationType {
String business();
long minValue() default 10L;
long maxValue() default 10000L;
}
@MyAnnotationType(business = "user")
public class Test4 {
@MyAnnotationType(business = "user", minValue = 100L)
private long value;
@MyAnnotationType(business = "user", minValue = 50L)
public long getValue(){
return value;
}
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
boolean isMyAnnotationType = Test4.class.isAnnotationPresent(MyAnnotationType.class);
if(isMyAnnotationType){
MyAnnotationType myAnnotationType = Test4.class.getAnnotation(MyAnnotationType.class);
System.out.println(myAnnotationType.business());
}
Field field = Test4.class.getDeclaredField("value");
if(field != null){
MyAnnotationType fieldAnnotationType = field.getAnnotation(MyAnnotationType.class);
System.out.println(fieldAnnotationType.minValue());
}
Method method = Test4.class.getDeclaredMethod("getValue");
if(method!=null){
MyAnnotationType[] myAnnotationTypes = method.getAnnotationsByType(MyAnnotationType.class);
for(MyAnnotationType annotationType: myAnnotationTypes){
System.out.println(annotationType.maxValue());
}
}
}
}
输出:
image.png
这个样例比较简单, 可以参考: https://www.jianshu.com/p/e374f938278c
3、 注解处理器
注解处理器(Annotation Processor)是javac的一个工具, 注解处理器用来在编译时扫描和处理注解。
Java有默认的注解处理器, 使用者也可以自定义注解处理器, 注册后使用注解处理器注解, 并最终达到注解本身起到的作用。
注解处理器的作用:
- 添加编译规则, 做编译检查, 如@Override。
- 修改已有的Java源文件, 如javaagent破解软件。
- 生成新的Java源文件。
3.1、注解处理器的原理
如上图所示, Java源代码的编译经过了3个步骤:
- 将源文件解析为抽象语法树。
- 调用已经注册的注解处理器。
- 生成字节码。
如果在第二步调用注解处理器生成了新的源文件, 那么编译器将重复第1、2步, 解析并处理新生成的源文件。每次重复我们称之为1轮(Round)。每一轮的输入都是已有的源文件, 循环往复,最终直到没有新的源文件产生, 全部处理成字节码。
Java中, 所有的注解处理器(Annotation Processor)都要实现javax.annotation.processing.Processor接口:
package javax.annotation.processing;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
public interface Processor {
Set<String> getSupportedOptions();
Set<String> getSupportedAnnotationTypes();
SourceVersion getSupportedSourceVersion();
void init(ProcessingEnvironment var1);
boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2);
Iterable<? extends Completion> getCompletions(Element var1, AnnotationMirror var2, ExecutableElement var3, String var4);
}
该接口有4个重要的方法:
-
init(ProcessingEnviroment var1):
init方法用来存放注解处理器的初始化代码。之所以不用构造器,是因为在Java编译器中, 注解处理器的实例是通过反射生成的。也正是因为反射API, 每个注解处理器都需要定义一个无参构造器。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。 -
getSupportedAnnotationTypes():
getSupportedAnnotationTypes方法返回该注解处理器支持的注解类型。 它的返回值是一个注解类型的集合, 包含本处理器支持处理的注解类的合法全称, 代表本注解处理器可以处理哪些注解。 -
getSupportedSourceVersion():
指定注解处理器支持的Java版本, 一般返回SourceVersion.latestSupported()。通常这个版本和所使用Java编译器的版本保持一致。 -
process(Set<? extends TypeElement> var1, RoundEnvironment var2):
process方法是最为关键的注解处理方法,相当于注解处理器的main()函数。扫描、评估和处理注解的代码, 以及生成Java源文件。 第一个参数 annotations 就是当前注解处理器支持的注解集合,第二个 roundEnv 表示当前的 APT 环境,其中提供了很多API,可以通过它获取到注解元素的一些信息。其中最重要的就是 getElementsAnnotatedWith 方法,通过它可以获取到所有使用了该注解的元素。
3.2、注解处理器的实现
JDK提供了一个实现Processor接口的抽象类AbstractProcessor。该抽象类实现了init、getSupportedAnnotationTypes、getSupportedSourceVersion3个方法。它的子类可通过注解SupportedAnnotationTypes、SupportedSourceVersion注解来声明注解处理器支持的注解类和Java版本。
我们尝试实现一个注解处理器, 这里定义了一个Getter注解, 但是实际上我们并没有实现具体的处理逻辑,只是试验以下整个过程。
3.2.1、 实现一个注解
package org.example.good.java.annotation.annotation.lombok;
import java.lang.annotation.*;
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}
3.2.2、 实现注解处理器
package org.example.good.java.annotation.processor.lombok;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
@SupportedAnnotationTypes({"org.example.good.java.annotation.annotation.lombok.Getter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class LombokProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "LombokProcessor ...... init.");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, String.format("annotation size: %d", set.size()));
if(set.isEmpty()){
return false;
}
for(TypeElement annotation: set) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotation %s has annotated %d class.",
annotation.getSimpleName(), elements.size()));
for(Element element: elements){
messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotated class: %s.", element.getSimpleName()));
}
}
return true;
}
}
- messager是一个工具类, 用来打印日志。
- process方法中,参数set是TypeElement的集合, 代表待处理的注解, 它有多个子类, 分别代表类、方法、字段等。
- process方法中, RoundEnvironment 参数可以通过注解获取被该注解修饰的元素。
- 可以通过SupportedAnnotationTypes注解定义注解处理器支持处理的注解, 是一个数组。
- 可以通过SupportedSourceVersion注解定义注解处理器支持处理的Java版本。
3.2.3、定义一个被注解的类
package org.example.good.java.model.vo;
import org.example.good.java.annotation.annotation.lombok.Getter;
@Getter
public class Person {
private String name;
private int age;
public Person(){}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
3.2.4 定义Main类
package org.example.good.java;
import org.example.good.java.model.vo.Person;
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("jack");
person.setAge(1);
System.out.println(person);
}
}
至此, 一个注解处理器的开发部分就完成了。
3.3、注解处理器的注册
注解处理器使用前需要先注册, 需要注意的是注解处理器在使用过程中,需要进行:
1)首先编译注解处理器源代码
2)使用注解处理器作为编译参数编译应用程序源代码
3)运行编译好的应用程序
否则会报出注解处理器找不到的错误:
服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider org.example.good.java.annotation.processor.lombok.LombokProcessor not found时抛出异常错误
注解处理器的注册方法有多种, 以上面的代码为例,分别叙述。
3.3.1、通过javac命令行注册
- 编译注解处理器
编译命令: javac -encoding UTF-8 -d dst processor路径
javac -encoding UTF-8 -d out\production src\mai
n\java\org\example\good\java\annotation\processor\lombok\LombokProcessor.java
此时,会在目标目录下生成processor的class文件, 以我的为例:
image.png
- 利用注解处理器编译应用程序源代码
编译命令:javac -encoding UTF-8 -cp processor编译好的代码位置 -processor processor类路径 -s 源代码路径 需要编译的应用程序源代码位置
javac -encoding UTF-8 -cp out\production -processor org.example.good.java.annotation.processor.lombok.LombokProcessor -d out\
production -s src\main\java src\main\java\org\example\good\java\model\vo\Person.java src\main\java\org\example\good\java\Main.java src\main\java\org\example\good\java\annot
ation\annotation\lombok\Getter.java
编译过程中, 会输出注解处理器中的打印日志, 以我的为例:
image.png
分别输出了LombokProcessor过程中init和process方法里面打印的日志内容。
编译完成后, 整体目录如下:
image.png
- 执行应用程序
java -cp out\production org.example.good.java.Main
执行输出:
image.png
由于我们在process方法并没有真的实现业务操作, 所以这块整体的结果和没有被注解修饰一样, 后续可以考虑实现类似lombok的Get方法。
3.3.2、通过spi配置注册
SPI全称Service Provider Interface, 是一种服务发现机制。通过在classpath路径下的META-INF/services文件夹查找文件, 自动加载文件里定义的类。
这一机制为很多框架扩展提供了可能, 比如在Dubbo、JDBC都使用到了该机制。
通过该方法注解处理器时需要预先将编译好的注解处理器的class文件压缩入java包中,并在jar包的配置文件中记录该注解处理器的保命和类名, 配置方式为在resources文件中创建META-INF/services目录, 然后在该目录下创建javax.annotation.processing.Processor文件, 该文件中记录的为自定义注解处理器的全限定类名。
如图:
image.png
image.png
3.3.2.1、定义及注解处理器
1.创建maven工程
定义一个maven工程,用来定义注解及注解处理器。
-
定义注解
定义一个注解@Getter, 此处声明注解的修饰目标时类、枚举、接口、字段等, 保留阶段为源码阶段。此处并未定义注解的成员, 这个注解用来为pojo的字段生成get方法。
image.png
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}
- 定义注解处理器
该注解处理器作为示例, 并未实现真正的处理逻辑。仅在process方法中打印出被注解的成员。
@SupportedAnnotationTypes({"org.good.annotation.Getter"})
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class LombokProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "LombokProcessor ...... init.");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, String.format("annotation size: %d", set.size()));
if(set.isEmpty()){
return false;
}
for(TypeElement annotation: set) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotation %s has annotated %d class.",
annotation.getSimpleName(), elements.size()));
for(Element element: elements){
messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotated class: %s.", element.getSimpleName()));
}
}
return true;
}
}
4、注册注解处理器
在resources中, 创建META-INF/services目录, 并在该目录下创建对应的javax.annotation.processing.Procesor文件, 该文件中写入需要注册的注解处理器全限定类名。
5、 使用maven工具进行编译该工程
直接编译会发生错误, 报告注解处理器找不到, 因为配置的注解处理器是要在编译阶段使用javax.annotation.processing.Procesor中定义的注解处理器去处理注解, 显然这个时候注解处理器还未编译好。
服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider org.good.processor.LombokProcessor not found时抛出异常错误
这个时候,我们需要显示的告诉javac为当前项目忽略annotation processor, 如下:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<proc>none</proc>
</configuration>
</plugin>
</plugins>
</build>
再执行编译就编译成功了。
image.png
3.3.2.2 使用注解处理器
-
创建maven工程
image.png -
创建注解的使用
package org.good.example.core;
import org.good.annotation.Getter;
@Getter
public class Person {
private String name;
private int age;
public Person(){}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
- 配置注解处理器
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>good-java-annotation</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.example</groupId>
<artifactId>good-java-annotation</artifactId>
<version>1.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
- 执行编译
需要注意,pom.xml中需要配置, 要不然注解处理器中的日志打印不出来。其次注解处理器中日志若设置为Error级别, 会认为编译失败。
<configuration>
<showWarnings>true</showWarnings>
</configuration>
image.png
可以看到注解处理器中定义的日志打印出来了, 并且识别到Person类被@Getter注解了。由于注解处理器中并没有实现真正添加get方法的逻辑, 所以显示不出效果, 以后考虑在实现。
3.3.3、 通过google autoservice实现注解处理器的注册
Google Service的实现原理和spi一样, Geoole Service通过@AutoService注解帮开发者实现了编写META-INF/services的过程, 它的底层原理也是应用的SPI 的规范, 实现了一个AutoServiceProcessor编译阶段获取被@AutoService注解的注解处理器, 自动生成相应的services文件。
- 定义注解@Getter
- 定义注解处理器
@AutoService(Processor.class)
@SupportedAnnotationTypes({"org.good.annotation.Getter"})
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class LombokProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "LombokProcessor ...... init.");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, String.format("annotation size: %d", set.size()));
if(set.isEmpty()){
return false;
}
for(TypeElement annotation: set) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotation %s has annotated %d class.",
annotation.getSimpleName(), elements.size()));
for(Element element: elements){
messager.printMessage(Diagnostic.Kind.NOTE, String.format("Annotated class: %s.", element.getSimpleName()));
}
}
return true;
}
}
- 配置注解处理器
<dependencies>
<dependency>
<groupId>com.googe.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.googe.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0</version>
</path>
</annotationProcessorPaths>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
- 编译
编译后的效果和使用spi编译的效果一致。
4、总结与实践
Java中的注解和Python的装饰器作用差不多, Java注解主要通过字节码和反射来影响应用程序的行为。 注解的作用主要影响编译和运行两个阶段, 编译器注解可以通过注册注解处理器来进行代码检查、字节码修改生成, 运行期注解则通过反射添加额外的业务逻辑。
网友评论