Java 注解

Java注解是Java5引入的重要语言特性之一,它可以提供描述程序所需元数据信息,而这些信息是无法使用Java语言进行表达的。

注解的引入可以使我们能够使用编译器来验证格式,并存储程序额外信息。

注解又称元数据,为我们在代码中添加信息提供了一种方法,使得我们能够在稍后某个时刻方便的访问这些数据。

注解在一定程度上是将元数据和源代码文件结合在一起,而无需使用额外的配置文件进行管理。

注解的语法很简单,除了@符号的使用,基本与Java固有的写法一致,Java5中内置了三个注解:

  1. @Override
    表示当前方法将覆盖父类中的方法,如果方法名拼错或方法签名与被覆盖的方法不一致,编译器会发出错误提示
  2. @Deprecated
    标记当前方法、类、参数为已过期,当使用已过期方法时,编译器会发出Warning提示
  3. @SuppressWarning
    关闭编译器Warning信息

大多数情况下,程序员主要是定义自己的注解,并编写自己的处理器来处理他们,以达到既定的目的。

1. 定义注解

使用Java内置元注解对自定义注解进行注解,并在需要添加元数据的地方添加自定义注解。

1.1. 元注解

Java中内置了四种元注解,专职负责注解其他注解

  1. @Target
    表示该注解可以用于哪些地方,可用的ElementType有:
    • TYPE 类、接口、枚举等类型
    • FIELD 属性说明
    • METHOD 类方法
    • PARAMETER 参数声明
    • CONSTRUCTOR 构造函数
    • LOCAL_VARIABLE 本地变量
    • ANNOTATION_TYPE 注解类型
    • PACKAGE 包
    • TYPE_PARAMETER 类型参数(泛型)1.8
    • TYPE_USE 类型 1.8
  2. @Retention
    表明在哪个级别保存该注解信息,可选的RetentionPolicy有:
    • SOURCE
      注解对编译器生效,但被编译器丢弃
    • CLASS
      注解在Class文件中生效,但被JVM丢失
    • RUNTIME
      在运行时保留注解,因此通过反射机制可以拿到注解信息
  3. @Document
    将此注解包含到JavaDoc中
  4. @Inherited
    允许子类继承父类的注解

1.2. 自定义注解

1.2.1. 定义注解

下面是一个自动生成DTO的注解,可见注解的定义看起来很像接口定义,事实上,与接口定义一样,注解也会编译成class文件。

除@符外注解定义就像一个空接口,其中@Target和@Retention尤为重要,@Target用于定义注解将用于什么地方,@Retention用来定义该注解在哪个级别上可用。

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTO {
String pkgName();
}

此注解,使用在类型之上,作用于SOURCE,允许子类继承,并将其包含到Javadoc中。

1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTOIgnore {
}

此注解为标记注解,没有设置注解元素,只做标记来用。

1.2.2. 注解元素

注解上一般会包含一些元素以表示某值,当处理注解时,程序或工具可用利用这些值。

GenDTO注解上有一个String元素pkgName,用于记录元信息,注解元素可以用的类型包括:

  • 所有基本类型(int、float、boolean等)
  • String
  • Class
  • Enum
  • Annotation
  • 以上类型的数组

如果使用其他类型,编译器会报错。

1.2.3. 注解默认值

编译器对元素的默认值有些过分苛刻。首先元素不能有不确定的值,及元素要么提供默认值,要么在使用的时候提供元素的值;其次,对于非基本类型的元素,都不能以null作为默认值。

这种约束使得处理器很难处理元素值缺失的情况,因为在注解中所有的元素都存在,并且都有相应的值。为了绕开这个限制,我们通常会定义一些特殊的值,如空字符串或负数,以表示该值不存在。

1.2.4. 注解不支持继承

不能用extends来继承某个@interface,这是一件比较遗憾的事。

2. 使用注解

从语法角度看,注解的使用方式几乎与修饰符(public、static、void等)的使用一模一样,一个比较大的区别是注解上可以设置注解元素,以便注解处理器使用。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@GenDTO(pkgName = "com.example.annotation.dto")
public class TestEntity {
private String name;
private Long id;
private Integer age;
private Date birthday;

@GenDTOIgnore
private List<String> address;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Integer getAge() {
return age;
}

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

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public List<String> getAddress() {
return address;
}

public void setAddress(List<String> address) {
this.address = address;
}
}

3. 定义注解处理器

常见的注解处理方案主要包括应用于SOURCE阶段的apt(annotation proccess tools)和应用于RUNTIME阶段的反射。

3.1. apt处理器

注解处理工具apt,是Sun公司为了帮助注解处理过程而提供的工具。apt和javac一样,被设计为操作Java源文件,而不是编译后的类。

默认情况下,apt会在处理完源文件后编译他们,如果在系统构建的过程中自动创建了一些新的源文件,该文件会在新一轮的注解处理中接受检查。该工具一轮轮的处理,直到不在有新的源文件产生为止,然后在编译所有的源文件。

我们定义的每一个注解都需要自己的处理器,apt工具可以将多个注解处理器组合起来,已完成比较复杂的应用场景。

在使用apt生成的注解处理器时,我们无法使用Java的反射机制,因为我们操作的是Java源文件,而不是编译后的类。

基于apt的注解处理器开发,主要包括两个步骤:

  1. 实现Processor接口定制自己的注解处理器
  2. 添加注解处理器相关配置

3.1.1. 自定义Processor

自定义Processor,需要提供一个公共的无参构造函数,框架工具通过该构造函数实例化Processor,并由工具统一调用。

Processor 交互流程

框架工具与Processor的交互流程如下:

  1. 如果不使用现有的Processor对象,则通过Processor的无参构造函数创建新的实例对象
  2. 工具调用init方法对Processor进行初始化
  3. 工具调用getSupportedAnnotationTypes、getSupportedOptions 和 getSupportedSourceVersion方法,了解注解具体的应用信息。这些方法只在每次运行时调用一次,并非每个处理都调用。
  4. 如果满足上述条件,调用Processor对象的process方法,进行注解处理。

Processor 核心方法

Processor核心方法如下:

  1. void init(ProcessingEnvironment processingEnv) 用处理器环境初始化 Processor
  2. Set getSupportedAnnotationTypes() 返回此 Processor 支持的注释类型的名称
  3. SourceVersion getSupportedSourceVersion() 返回此注释 Processor 支持的最新的源版本
  4. Set getSupportedOptions() 返回此 Processor 识别的选项
  5. boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 处理先前产生的类型元素上的注释类型集,并返回这些注释是否由此 Processor处理
  6. Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 向工具框架返回某一注释的建议 completion 迭代

AbstractProcessor

一般情况下,我们很少直接实现Processor,而是继承AbstractProcessor,并在它基础上进行扩展。
AbstractProcessor对Processor接口进行了扩展,以方便扩展。

AbstractProcessor核心方法:

  1. void init(ProcessingEnvironment processingEnv)
    环境初始化方法,将processingEnv字段设置为 processingEnv 参数的值
  2. Set getSupportedAnnotationTypes() 如果 processor类是使用SupportedAnnotationTypes注释的,则返回一个不可修改的集合,该集合具有与注释相的字符串集
  3. SourceVersion getSupportedSourceVersion()如果 processor类是使用SupportedSourceVersion注释的,则返回注释中的源版本
  4. Set getSupportedOptions() 如果 processor类是使用SupportedOptions注释的,则返回一个不可修改的集合,该集合具有与注释相的字符串集
  5. Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 返回一个空的 completion 迭代

总体来说,AbstractProcessor主要做了以下几件事:

  • init时保存ProcessingEnvironment
  • 使用注解方式实现getSupported***相关方法
  • getCompletions返回新迭代

至此,对于Processor的实现主要集中在procosse方法。

ProcessingEnvironment

ProcessingEnvironment主要用于Processor初始化,将上下文信息传递到Processor中,以方便后续操作。

核心方法如下:

  1. Elements getElementUtils() 返回用来在元素上进行操作的某些实用工具方法的实现
  2. Filer getFiler() 返回用来创建新源、类或辅助文件的 Filer
  3. Locale getLocale() 返回当前语言环境;如果没有有效的语言环境,则返回null
  4. Messager getMessager() 返回用来报告错误、警报和其他通知的 Messager
  5. Map<String,String> getOptions()返回传递给注释处理工具的特定于 processor 的选项
  6. SourceVersion getSourceVersion() 返回任何生成的源和类文件应该符合的源版本
  7. Types getTypeUtils()返回用来在类型上进行操作的某些实用工具方法的实现

process

这相当于每个处理器的主函数main()。你在这里写你的扫描、收集和处理注解的代码,以及生成Java文件。

一般情况下,process方法的处理逻辑如下:

  1. 提取注解中的元信息
  2. 生成java代码,通过拼接字符串或使用成熟的代码生成器生成java代码
  3. 将java代码通过filer写回工具,等待编译器处理

3.1.2. Processor配置

自定义处理器最终会打包成jar文件,由其他项目在编译环节调用,为了让编译器能识别自定义的Processor,需要使用SPI技术添加相关配置信息。

在META-INF/services目录下,新建javax.annotation.processing.Processor文件,每行一个,将注解器添加到该文件。

如javax.annotation.processing.Processor文件内容:

1
2
3
xxxx.GenCodeBasedEnumConverterProcessor
xxx.GenDTOProcessor
xxx.GenUpdaterProcessor

该文件打包至输出jar中,然后由编译器在编译环节使用。

3.2 反射处理器

Java5中对反射相关的接口进行扩展,用以通过反射机制获取RUNTIME阶段的注解信息,从而实现基于反射的注解处理器。

3.2.1 Annotation

Java使用Annotation类来表示程序中的注解,及所有注解都是Annotation接口的子类。

3.2.2. AnnotatedElement

Java新增AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,并提供统一的Annotation访问方式,赋予API通过反射获取Annotation的能力,当一个Annotation类型被定义为运行时后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的下列方法来访问Annotation信息:

  1. T getAnnotation(Class annotationClass)返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
  2. Annotation[] getAnnotations() 返回该程序元素上存在的所有注解
  3. boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false
  4. Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

AnnotatedElement子类涵盖所有可以出现Annotation的地方,其中包括:

  1. Constructor 构造函数
  2. Method 方法
  3. Class 类型
  4. Field 字段
  5. Package 包
  6. Parameter 参数
  7. AnnotatedParameterizedType 泛型
  8. AnnotatedTypeVariable 变量
  9. AnnotatedArrayType 数组类型
  10. AnnotatedWildcardType

4. Java注解实战

4.1. 基于apt的代码生成器

现在系统都有严格的分层标准,每层负责不同的职责,以最大程度的解耦,其中最为核心的应该是Domain层(也称领域层),其上是application或service层,为了保证domain层的安全性,不允许直接将其进行暴露,最常用的一种方式便是,将其转化为DTO在并外提供服务。

其中Domain对象与DTO之间结构同步,耗费了很大的人力资源,而且大多情况下都是些机械性的代码,没有什么太大的挑战。而这个场景便是代码生成器所擅长的领域。

4.1.1. 设计目标

通过基于注解的代码生成器,根据Domain对象的结构,自动生成BaseDomainDTO作为父类,用于维护通用的结构,DomainDTO从BaseDomainDTO中继承,在享受通用结构的同时,为特殊需求提供扩展点。

4.1.2. 自定义注解

自定义注解包括GenDTO和GenDTOIgnore。

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTO {
String pkgName();
}

标记于Domain类上,用于说明该类需要生成BaseDomainDTO

1
2
3
4
5
6
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTOIgnore {
}

注解与Domain中的字段上,在生成BaseDomainDTO时,忽略该字段

4.1.3. 自定义注解处理器

自定义注解处理器,在编译过程中,读取注解信息,并完成BaseDomainDTO的代码生成。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
public abstract class BaseProcessor<A extends Annotation> extends AbstractProcessor {
/**
* 需要处理的注解类(GenDTO)
*/
private final Class aClass;
/**
* 用于回写新生成的java文件
*/
private Filer filer;

private Messager messager;

public BaseProcessor(Class<A> aClass) {
this.aClass = aClass;
}

/**
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return Sets.newHashSet(aClass.getCanonicalName());
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.filer = processingEnv.getFiler();
this.messager = processingEnv.getMessager();
}



@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取所有标记该注解的元素
Set<Element> elements = roundEnv.getElementsAnnotatedWith(this.aClass);
for (Element element : ElementFilter.typesIn(elements)){
A a = (A) element.getAnnotation(this.aClass);
// 循环处理每一个标记对象
foreachClass(a, element, roundEnv);
}
return false;
}

protected abstract void foreachClass(A a, Element element, RoundEnvironment roundEnv);

/**
* 获取元素的所有字段信息
* @param element
* @param filter
* @return
*/
protected Set<TypeAndName> findFields(Element element, Predicate<Element> filter){
return ElementFilter.fieldsIn(element.getEnclosedElements()).stream()
.filter(filter)
.map(variableElement -> new TypeAndName(variableElement))
.collect(Collectors.toSet());
}

/**
* 获取元素中所有的Getter方法
* @param element
* @param filter
* @return
*/
protected Set<TypeAndName> findGetter(Element element, Predicate<Element> filter){
return ElementFilter.methodsIn(element.getEnclosedElements()).stream()
.filter(filter)
.filter(executableElement -> isGetter(executableElement.getSimpleName().toString()))
.map(executableElement -> new TypeAndName(executableElement))
.collect(Collectors.toSet());

}

private boolean isGetter(String s) {
return s.startsWith("id") || s.startsWith("get");
}

/**
* 获取基于注解的忽略过滤器
* @param iClass
* @param <I>
* @return
*/
protected <I extends Annotation> Predicate<Element> filterForIgnore(Class<I> iClass){
return new IgnoreFilter<I>(iClass);
}

/**
* 忽略过滤器,如果元素上添加了Ignore注解,对其进行忽略
* @param <I>
*/
protected static class IgnoreFilter<I extends Annotation> implements Predicate<Element>{
private final Class<I> iClass;

public IgnoreFilter(Class<I> iClass) {
this.iClass = iClass;
}

@Override
public boolean test(Element variableElement) {
return variableElement.getAnnotation(iClass) == null;
}
}


/**
* 生成Java文件
* @param typeSpecBuilder
* @param pkgName
*/
protected void createJavaFile(TypeSpec.Builder typeSpecBuilder, String pkgName) {
try {
JavaFile javaFile = JavaFile.builder(pkgName, typeSpecBuilder.build())
.addFileComment(" This codes are generated automatically. Do not modify!")
.build();
javaFile.writeTo(filer);
System.out.print(javaFile);
this.messager.printMessage(WARNING, javaFile.toString());
} catch (IOException e) {
e.printStackTrace();
}
}

@Value
protected static class TypeAndName{
private final String name;
private final TypeName type;

public TypeAndName(VariableElement variableElement) {
this.name = variableElement.getSimpleName().toString();
this.type = TypeName.get(variableElement.asType());

}

public TypeAndName(ExecutableElement executableElement) {
this.name = getFieldName(executableElement.getSimpleName().toString());
this.type = TypeName.get(executableElement.getReturnType());
}

private String getFieldName(String s) {
String r = null;
if (s.startsWith("get")){
r = s.substring(3, s.length());
}else if (s.startsWith("is")){
r = s.substring(2, s.length());
}else {
r = s;
}
return r.substring(0, 1).toLowerCase() + r.substring(1, r.length());
}
}
}

BaseProcessor继承自AbstractProcessor,对通用行为进行封装。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@AutoService(Processor.class)
public class GenDTOProcessor extends BaseProcessor<GenDTO> {
public GenDTOProcessor() {
super(GenDTO.class);
}

@Override
protected void foreachClass(GenDTO genDTO, Element element, RoundEnvironment roundEnv) {
String className = "Base" + element.getSimpleName().toString() + "DTO";

Set<TypeAndName> typeAndNames = Sets.newHashSet();
// 获取元素中字段信息
Set<TypeAndName> fields = findFields(element, filterForIgnore(GenDTOIgnore.class));
typeAndNames.addAll(fields);

// 获取元素中的Getter信息
Set<TypeAndName> getters = findGetter(element, filterForIgnore(GenDTOIgnore.class));
typeAndNames.addAll(getters);

// 生成Java类, 类名为className, 添加Data注解,并使用public、abstract关键字描述
TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className)
.addAnnotation(Data.class)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);

// 生成构造函数
MethodSpec.Builder cMethodSpecBuilder = MethodSpec.constructorBuilder()
.addParameter(TypeName.get(element.asType()), "source")
.addModifiers(Modifier.PROTECTED);

for (TypeAndName typeAndName : typeAndNames) {
// 声明BaseDomainDTO中的字段信息,Setter方法设置为private,Getter方法设置为public
FieldSpec fieldSpec = FieldSpec.builder(typeAndName.getType(), typeAndName.getName(), Modifier.PRIVATE)
.addAnnotation(AnnotationSpec.builder(Setter.class)
.addMember("value", "$T.PRIVATE", AccessLevel.class)
.build())
.addAnnotation(AnnotationSpec.builder(Getter.class)
.addMember("value", "$T.PUBLIC", AccessLevel.class)
.build())
.build();
typeSpecBuilder.addField(fieldSpec);

String fieldName = typeAndName.getName().substring(0, 1).toUpperCase() + typeAndName.getName().substring(1, typeAndName.getName().length());

// 构造函数中添加设置语句
cMethodSpecBuilder.addStatement("this.set$L(source.get$L())", fieldName, fieldName);
}

// 将构造函数添加到类中
typeSpecBuilder.addMethod(cMethodSpecBuilder.build());

// 生成Java文件
String pkgName = genDTO.pkgName();
createJavaFile(typeSpecBuilder, pkgName);

}
}

生成BaseDomainDTO的核心逻辑全部在GenDTOProcessor中,详见内部注解。

有一个点需要特殊注意,GenDTOProcessor类上添加了@AutoService(Processor.class)注解,这是google auto-service中的注解,用于完成Processor服务的自动注册,及在META-INF/services/javax.annotation.processing.Processor文件中添加com.example.annotation.dto.GenDTOProcessor配置内容。

4.1.4. 使用注解

根据实际需求,在对于的class中添加注解即可。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 设置BaseTestEntityDTO所存在的包
*/
@GenDTO(pkgName = "com.example.annotation.dto")
public class TestEntity {
private String name;
private Long id;
private Integer age;
private Date birthday;

/**
* 忽略该字段
*/
@GenDTOIgnore
private List<String> address;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Integer getAge() {
return age;
}

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

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

@GenDTOIgnore
public List<String> getAddress() {
return address;
}

public void setAddress(List<String> address) {
this.address = address;
}
}

执行maven编译,自动生成BaseTestEntityDTO,并自动编译。
生成代码如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
//  This codes are generated automatically. Do not modify!
package com.example.annotation.dto;

import com.example.annotation.entity.TestEntity;
import java.lang.Integer;
import java.lang.Long;
import java.lang.String;
import java.util.Date;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

@Data
public abstract class BaseTestEntityDTO {

@Setter(AccessLevel.PRIVATE)
@Getter(AccessLevel.PUBLIC)
private Integer age;

@Setter(AccessLevel.PRIVATE)
@Getter(AccessLevel.PUBLIC)
private Date birthday;

@Setter(AccessLevel.PRIVATE)
@Getter(AccessLevel.PUBLIC)
private Long id;

@Setter(AccessLevel.PRIVATE)
@Getter(AccessLevel.PUBLIC)
private String name;

protected BaseTestEntityDTO(TestEntity source) {
this.setAge(source.getAge());
this.setBirthday(source.getBirthday());
this.setId(source.getId());
this.setName(source.getName());
}
}

此时便可以据此创建TestEntityDTO。

1
2
3
4
5
6
7
8
9
@Data
public class TestEntityDTO extends BaseTestEntityDTO{
private List<String> address;

private TestEntityDTO(TestEntity source) {
super(source);
this.address = Lists.newArrayList(source.getAddress());
}
}

对于需要特殊处理的属性,进行特殊处理。如address的copy。

4.2. Spring基于反射的RequestMapping

Spring一直是java注解的使用大户,特别是RUNTIME级别、基于反射的注解,几乎无处不在。

4.2.1. 设计目标

模拟SpringMVC中的RequestMapping注解,收集path与method的映射关系。

4.2.2. 自定义注解

自定义RequestMappiing注解。

1
2
3
4
5
6
7
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RequestMapping {
String path() default "";
}

Retention设置为RUNTIME,以通过反射获取注解信息。

4.2.3. 自定义注解处理器

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class RequestMappingParser<T> {
private final Class<T> tClass;

public RequestMappingParser(Class<T> tClass){
this.tClass = tClass;
}

public Set<ParserResult> parse(){
RequestMapping cRequestMapping = this.tClass.getAnnotation(RequestMapping.class);
String rootPath = cRequestMapping != null ? cRequestMapping.path() : "";
if (!rootPath.startsWith("/")){
rootPath = "/" + rootPath;
}

Set<ParserResult> parserResults = Sets.newHashSet();
for (Method method : this.tClass.getMethods()){
RequestMapping mRequestMapping = method.getAnnotation(RequestMapping.class);
if (mRequestMapping == null){
continue;
}

String mPath = mRequestMapping.path();
if ("".equals(mPath)){
continue;
}
String path = null;
if (mPath.startsWith("/")){
path = rootPath + mPath;
}else {
path = rootPath + "/" + mPath;
}
parserResults.add(new ParserResult(path, method));
}
return parserResults;
}


@Data
static class ParserResult{
private final String path;
private final Method method;
}
}

4.2.4. 使用注解

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
@RequestMapping(path = "/root")
public class RequestMappingObject {

@RequestMapping(path = "method")
public void method(){

}

@RequestMapping(path = "method1")
public void method1(){

}

@RequestMapping(path = "method2")
public void method2(){

}

@RequestMapping(path = "method3")
public void method3(){

}

@RequestMapping(path = "method4")
public void method4(){

}
}

4.2.5. 处理结果

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RequestMappingTest {
@Test
public void print(){
new RequestMappingParser<>(RequestMappingObject.class)
.parse()
.forEach(parserResult -> {
System.out.print(parserResult.getPath());
System.out.print("-->");
System.out.print(parserResult.getMethod());
System.out.println();
});
}
}

运行结果如下:

/root/method2–>public void com.example.annotation.reflect.RequestMappingObject.method2()
/root/method3–>public void com.example.annotation.reflect.RequestMappingObject.method3()
/root/method–>public void com.example.annotation.reflect.RequestMappingObject.method()
/root/method4–>public void com.example.annotation.reflect.RequestMappingObject.method4()
/root/method1–>public void com.example.annotation.reflect.RequestMappingObject.method1()

5. 总结

总体来说,注解从两个层面简化了硬编码的工作量,给一些反复工作提供了另一种解决方案。基于注解的代码生成器便是这个领域的一大利器;注解与反射的结合,可以在运行时获取注解元数据,给程序的动态扩展提供了一个想象空间,特别是Spring等框架,将其用到了极致。

文章所用代码已经上传至码云,如有需要请自行下载:https://gitee.com/litao851025/books/tree/master/annotation-demo

wenxinzizhu wechat
扫一扫,添加我的微信,一起交流共同成长(备注为技术学习)