Java注解是Java5引入的重要语言特性之一,它可以提供描述程序所需元数据信息,而这些信息是无法使用Java语言进行表达的。
注解的引入可以使我们能够使用编译器来验证格式,并存储程序额外信息。
注解又称元数据,为我们在代码中添加信息提供了一种方法,使得我们能够在稍后某个时刻方便的访问这些数据。
注解在一定程度上是将元数据和源代码文件结合在一起,而无需使用额外的配置文件进行管理。
注解的语法很简单,除了@符号的使用,基本与Java固有的写法一致,Java5中内置了三个注解:
-
@Override
表示当前方法将覆盖父类中的方法,如果方法名拼错或方法签名与被覆盖的方法不一致,编译器会发出错误提示 -
@Deprecated
标记当前方法、类、参数为已过期,当使用已过期方法时,编译器会发出Warning提示 -
@SuppressWarning
关闭编译器Warning信息
大多数情况下,程序员主要是定义自己的注解,并编写自己的处理器来处理他们,以达到既定的目的。
1. 定义注解
使用Java内置元注解对自定义注解进行注解,并在需要添加元数据的地方添加自定义注解。
1.1. 元注解
Java中内置了四种元注解,专职负责注解其他注解
-
@Target 表示该注解可以用于哪些地方,可用的ElementType有:
-
TYPE 类、接口、枚举等类型
-
FIELD 属性说明
-
METHOD 类方法
-
PARAMETER 参数声明
-
CONSTRUCTOR 构造函数
-
LOCAL_VARIABLE 本地变量
-
ANNOTATION_TYPE 注解类型
-
PACKAGE 包
-
TYPE_PARAMETER 类型参数(泛型)1.8
-
TYPE_USE 类型 1.8
-
@Retention 表明在哪个级别保存该注解信息,可选的RetentionPolicy有:
-
SOURCE
注解对编译器生效,但被编译器丢弃 -
CLASS
注解在Class文件中生效,但被JVM丢失 -
RUNTIME
在运行时保留注解,因此通过反射机制可以拿到注解信息 -
@Document
将此注解包含到JavaDoc中 -
@Inherited
允许子类继承父类的注解
1.2. 自定义注解
1.2.1. 定义注解
下面是一个自动生成DTO的注解,可见注解的定义看起来很像接口定义,事实上,与接口定义一样,注解也会编译成class文件。
除@符外注解定义就像一个空接口,其中@Target和@Retention尤为重要,@Target用于定义注解将用于什么地方,@Retention用来定义该注解在哪个级别上可用。
1@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.SOURCE)
3@Inherited
4@Documented
5public @interface GenDTO {
6 String pkgName();
7}
此注解,使用在类型之上,作用于SOURCE,允许子类继承,并将其包含到Javadoc中。
1@Target(ElementType.FIELD)
2@Retention(RetentionPolicy.SOURCE)
3@Inherited
4@Documented
5public @interface GenDTOIgnore {
6}
此注解为标记注解,没有设置注解元素,只做标记来用。
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@GenDTO(pkgName = "com.example.annotation.dto")
2public class TestEntity {
3 private String name;
4 private Long id;
5 private Integer age;
6 private Date birthday;
7
8 @GenDTOIgnore
9 private List<String> address;
10
11 public String getName() {
12 return name;
13 }
14
15 public void setName(String name) {
16 this.name = name;
17 }
18
19 public Long getId() {
20 return id;
21 }
22
23 public void setId(Long id) {
24 this.id = id;
25 }
26
27 public Integer getAge() {
28 return age;
29 }
30
31 public void setAge(Integer age) {
32 this.age = age;
33 }
34
35 public Date getBirthday() {
36 return birthday;
37 }
38
39 public void setBirthday(Date birthday) {
40 this.birthday = birthday;
41 }
42
43 public List<String> getAddress() {
44 return address;
45 }
46
47 public void setAddress(List<String> address) {
48 this.address = address;
49 }
50}
3. 定义注解处理器
常见的注解处理方案主要包括应用于SOURCE阶段的apt(annotation proccess tools)和应用于RUNTIME阶段的反射。
3.1. apt处理器
注解处理工具apt,是Sun公司为了帮助注解处理过程而提供的工具。apt和javac一样,被设计为操作Java源文件,而不是编译后的类。
默认情况下,apt会在处理完源文件后编译他们,如果在系统构建的过程中自动创建了一些新的源文件,该文件会在新一轮的注解处理中接受检查。该工具一轮轮的处理,直到不在有新的源文件产生为止,然后在编译所有的源文件。
我们定义的每一个注解都需要自己的处理器,apt工具可以将多个注解处理器组合起来,已完成比较复杂的应用场景。
在使用apt生成的注解处理器时,我们无法使用Java的反射机制,因为我们操作的是Java源文件,而不是编译后的类。
基于apt的注解处理器开发,主要包括两个步骤:
-
实现Processor接口定制自己的注解处理器
-
添加注解处理器相关配置
3.1.1. 自定义Processor
自定义Processor,需要提供一个公共的无参构造函数,框架工具通过该构造函数实例化Processor,并由工具统一调用。
Processor 交互流程
框架工具与Processor的交互流程如下:
-
如果不使用现有的Processor对象,则通过Processor的无参构造函数创建新的实例对象
-
工具调用init方法对Processor进行初始化
-
工具调用getSupportedAnnotationTypes、getSupportedOptions 和 getSupportedSourceVersion方法,了解注解具体的应用信息。这些方法只在每次运行时调用一次,并非每个处理都调用。
-
如果满足上述条件,调用Processor对象的process方法,进行注解处理。
Processor 核心方法
Processor核心方法如下:
-
void init(ProcessingEnvironment processingEnv) 用处理器环境初始化 Processor
-
Set
getSupportedAnnotationTypes() 返回此 Processor 支持的注释类型的名称 SourceVersion getSupportedSourceVersion() 返回此注释 Processor 支持的最新的源版本
-
Set
getSupportedOptions() 返回此 Processor 识别的选项 boolean process(Set annotations, RoundEnvironment roundEnv) 处理先前产生的类型元素上的注释类型集,并返回这些注释是否由此 Processor处理
-
Iterable getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 向工具框架返回某一注释的建议 completion 迭代
AbstractProcessor
一般情况下,我们很少直接实现Processor,而是继承AbstractProcessor,并在它基础上进行扩展。
AbstractProcessor对Processor接口进行了扩展,以方便扩展。
AbstractProcessor核心方法:
-
void init(ProcessingEnvironment processingEnv)
环境初始化方法,将processingEnv字段设置为 processingEnv 参数的值 -
Set
getSupportedAnnotationTypes() 如果 processor类是使用SupportedAnnotationTypes注释的,则返回一个不可修改的集合,该集合具有与注释相的字符串集 SourceVersion getSupportedSourceVersion()如果 processor类是使用SupportedSourceVersion注释的,则返回注释中的源版本
-
Set
getSupportedOptions() 如果 processor类是使用SupportedOptions注释的,则返回一个不可修改的集合,该集合具有与注释相的字符串集 Iterable getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 返回一个空的 completion 迭代
总体来说,AbstractProcessor主要做了以下几件事:
-
init时保存ProcessingEnvironment
-
使用注解方式实现getSupported***相关方法
-
getCompletions返回新迭代
至此,对于Processor的实现主要集中在procosse方法。
ProcessingEnvironment
ProcessingEnvironment主要用于Processor初始化,将上下文信息传递到Processor中,以方便后续操作。
核心方法如下:
-
Elements getElementUtils() 返回用来在元素上进行操作的某些实用工具方法的实现
-
Filer getFiler() 返回用来创建新源、类或辅助文件的 Filer
-
Locale getLocale() 返回当前语言环境;如果没有有效的语言环境,则返回null
-
Messager getMessager() 返回用来报告错误、警报和其他通知的 Messager
-
Map
getOptions()返回传递给注释处理工具的特定于 processor 的选项 SourceVersion getSourceVersion() 返回任何生成的源和类文件应该符合的源版本
-
Types getTypeUtils()返回用来在类型上进行操作的某些实用工具方法的实现
process
这相当于每个处理器的主函数main()。你在这里写你的扫描、收集和处理注解的代码,以及生成Java文件。
一般情况下,process方法的处理逻辑如下:
-
提取注解中的元信息
-
生成java代码,通过拼接字符串或使用成熟的代码生成器生成java代码
-
将java代码通过filer写回工具,等待编译器处理
3.1.2. Processor配置
自定义处理器最终会打包成jar文件,由其他项目在编译环节调用,为了让编译器能识别自定义的Processor,需要使用SPI技术添加相关配置信息。
在META-INF/services目录下,新建javax.annotation.processing.Processor文件,每行一个,将注解器添加到该文件。
如javax.annotation.processing.Processor文件内容:
1xxxx.GenCodeBasedEnumConverterProcessor
2xxx.GenDTOProcessor
3xxx.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信息:
T getAnnotation(Class annotationClass)返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null -
Annotation[] getAnnotations() 返回该程序元素上存在的所有注解
-
boolean is AnnotationPresent(Class annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false
-
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
AnnotatedElement子类涵盖所有可以出现Annotation的地方,其中包括:
-
Constructor 构造函数
-
Method 方法
-
Class 类型
-
Field 字段
-
Package 包
-
Parameter 参数
-
AnnotatedParameterizedType 泛型
-
AnnotatedTypeVariable 变量
-
AnnotatedArrayType 数组类型
-
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@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.SOURCE)
3@Inherited
4@Documented
5public @interface GenDTO {
6 String pkgName();
7}
标记于Domain类上,用于说明该类需要生成BaseDomainDTO
1@Target({ElementType.FIELD, ElementType.METHOD})
2@Retention(RetentionPolicy.SOURCE)
3@Inherited
4@Documented
5public @interface GenDTOIgnore {
6}
注解与Domain中的字段上,在生成BaseDomainDTO时,忽略该字段
4.1.3. 自定义注解处理器
自定义注解处理器,在编译过程中,读取注解信息,并完成BaseDomainDTO的代码生成。
1public abstract class BaseProcessor<A extends Annotation> extends AbstractProcessor {
2 /**
3 * 需要处理的注解类(GenDTO)
4 */
5 private final Class aClass;
6 /**
7 * 用于回写新生成的java文件
8 */
9 private Filer filer;
10
11 private Messager messager;
12
13 public BaseProcessor(Class<A> aClass) {
14 this.aClass = aClass;
15 }
16
17 /**
18 *
19 * @return
20 */
21 @Override
22 public Set<String> getSupportedAnnotationTypes() {
23 return Sets.newHashSet(aClass.getCanonicalName());
24 }
25
26 @Override
27 public SourceVersion getSupportedSourceVersion() {
28 return SourceVersion.latestSupported();
29 }
30
31 @Override
32 public synchronized void init(ProcessingEnvironment processingEnv) {
33 super.init(processingEnv);
34 this.filer = processingEnv.getFiler();
35 this.messager = processingEnv.getMessager();
36 }
37
38
39
40 @Override
41 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
42 // 获取所有标记该注解的元素
43 Set<Element> elements = roundEnv.getElementsAnnotatedWith(this.aClass);
44 for (Element element : ElementFilter.typesIn(elements)){
45 A a = (A) element.getAnnotation(this.aClass);
46 // 循环处理每一个标记对象
47 foreachClass(a, element, roundEnv);
48 }
49 return false;
50 }
51
52 protected abstract void foreachClass(A a, Element element, RoundEnvironment roundEnv);
53
54 /**
55 * 获取元素的所有字段信息
56 * @param element
57 * @param filter
58 * @return
59 */
60 protected Set<TypeAndName> findFields(Element element, Predicate<Element> filter){
61 return ElementFilter.fieldsIn(element.getEnclosedElements()).stream()
62 .filter(filter)
63 .map(variableElement -> new TypeAndName(variableElement))
64 .collect(Collectors.toSet());
65 }
66
67 /**
68 * 获取元素中所有的Getter方法
69 * @param element
70 * @param filter
71 * @return
72 */
73 protected Set<TypeAndName> findGetter(Element element, Predicate<Element> filter){
74 return ElementFilter.methodsIn(element.getEnclosedElements()).stream()
75 .filter(filter)
76 .filter(executableElement -> isGetter(executableElement.getSimpleName().toString()))
77 .map(executableElement -> new TypeAndName(executableElement))
78 .collect(Collectors.toSet());
79
80 }
81
82 private boolean isGetter(String s) {
83 return s.startsWith("id") || s.startsWith("get");
84 }
85
86 /**
87 * 获取基于注解的忽略过滤器
88 * @param iClass
89 * @param <I>
90 * @return
91 */
92 protected <I extends Annotation> Predicate<Element> filterForIgnore(Class<I> iClass){
93 return new IgnoreFilter<I>(iClass);
94 }
95
96 /**
97 * 忽略过滤器,如果元素上添加了Ignore注解,对其进行忽略
98 * @param <I>
99 */
100 protected static class IgnoreFilter<I extends Annotation> implements Predicate<Element>{
101 private final Class<I> iClass;
102
103 public IgnoreFilter(Class<I> iClass) {
104 this.iClass = iClass;
105 }
106
107 @Override
108 public boolean test(Element variableElement) {
109 return variableElement.getAnnotation(iClass) == null;
110 }
111 }
112
113
114 /**
115 * 生成Java文件
116 * @param typeSpecBuilder
117 * @param pkgName
118 */
119 protected void createJavaFile(TypeSpec.Builder typeSpecBuilder, String pkgName) {
120 try {
121 JavaFile javaFile = JavaFile.builder(pkgName, typeSpecBuilder.build())
122 .addFileComment(" This codes are generated automatically. Do not modify!")
123 .build();
124 javaFile.writeTo(filer);
125 System.out.print(javaFile);
126 this.messager.printMessage(WARNING, javaFile.toString());
127 } catch (IOException e) {
128 e.printStackTrace();
129 }
130 }
131
132 @Value
133 protected static class TypeAndName{
134 private final String name;
135 private final TypeName type;
136
137 public TypeAndName(VariableElement variableElement) {
138 this.name = variableElement.getSimpleName().toString();
139 this.type = TypeName.get(variableElement.asType());
140
141 }
142
143 public TypeAndName(ExecutableElement executableElement) {
144 this.name = getFieldName(executableElement.getSimpleName().toString());
145 this.type = TypeName.get(executableElement.getReturnType());
146 }
147
148 private String getFieldName(String s) {
149 String r = null;
150 if (s.startsWith("get")){
151 r = s.substring(3, s.length());
152 }else if (s.startsWith("is")){
153 r = s.substring(2, s.length());
154 }else {
155 r = s;
156 }
157 return r.substring(0, 1).toLowerCase() + r.substring(1, r.length());
158 }
159 }
160}
BaseProcessor继承自AbstractProcessor,对通用行为进行封装。
1@AutoService(Processor.class)
2public class GenDTOProcessor extends BaseProcessor<GenDTO> {
3 public GenDTOProcessor() {
4 super(GenDTO.class);
5 }
6
7 @Override
8 protected void foreachClass(GenDTO genDTO, Element element, RoundEnvironment roundEnv) {
9 String className = "Base" + element.getSimpleName().toString() + "DTO";
10
11 Set<TypeAndName> typeAndNames = Sets.newHashSet();
12 // 获取元素中字段信息
13 Set<TypeAndName> fields = findFields(element, filterForIgnore(GenDTOIgnore.class));
14 typeAndNames.addAll(fields);
15
16 // 获取元素中的Getter信息
17 Set<TypeAndName> getters = findGetter(element, filterForIgnore(GenDTOIgnore.class));
18 typeAndNames.addAll(getters);
19
20 // 生成Java类, 类名为className, 添加Data注解,并使用public、abstract关键字描述
21 TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className)
22 .addAnnotation(Data.class)
23 .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
24
25 // 生成构造函数
26 MethodSpec.Builder cMethodSpecBuilder = MethodSpec.constructorBuilder()
27 .addParameter(TypeName.get(element.asType()), "source")
28 .addModifiers(Modifier.PROTECTED);
29
30 for (TypeAndName typeAndName : typeAndNames) {
31 // 声明BaseDomainDTO中的字段信息,Setter方法设置为private,Getter方法设置为public
32 FieldSpec fieldSpec = FieldSpec.builder(typeAndName.getType(), typeAndName.getName(), Modifier.PRIVATE)
33 .addAnnotation(AnnotationSpec.builder(Setter.class)
34 .addMember("value", "$T.PRIVATE", AccessLevel.class)
35 .build())
36 .addAnnotation(AnnotationSpec.builder(Getter.class)
37 .addMember("value", "$T.PUBLIC", AccessLevel.class)
38 .build())
39 .build();
40 typeSpecBuilder.addField(fieldSpec);
41
42 String fieldName = typeAndName.getName().substring(0, 1).toUpperCase() + typeAndName.getName().substring(1, typeAndName.getName().length());
43
44 // 构造函数中添加设置语句
45 cMethodSpecBuilder.addStatement("this.set$L(source.get$L())", fieldName, fieldName);
46 }
47
48 // 将构造函数添加到类中
49 typeSpecBuilder.addMethod(cMethodSpecBuilder.build());
50
51 // 生成Java文件
52 String pkgName = genDTO.pkgName();
53 createJavaFile(typeSpecBuilder, pkgName);
54
55 }
56}
生成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 * 设置BaseTestEntityDTO所存在的包
3 */
4@GenDTO(pkgName = "com.example.annotation.dto")
5public class TestEntity {
6 private String name;
7 private Long id;
8 private Integer age;
9 private Date birthday;
10
11 /**
12 * 忽略该字段
13 */
14 @GenDTOIgnore
15 private List<String> address;
16
17 public String getName() {
18 return name;
19 }
20
21 public void setName(String name) {
22 this.name = name;
23 }
24
25 public Long getId() {
26 return id;
27 }
28
29 public void setId(Long id) {
30 this.id = id;
31 }
32
33 public Integer getAge() {
34 return age;
35 }
36
37 public void setAge(Integer age) {
38 this.age = age;
39 }
40
41 public Date getBirthday() {
42 return birthday;
43 }
44
45 public void setBirthday(Date birthday) {
46 this.birthday = birthday;
47 }
48
49 @GenDTOIgnore
50 public List<String> getAddress() {
51 return address;
52 }
53
54 public void setAddress(List<String> address) {
55 this.address = address;
56 }
57}
执行maven编译,自动生成BaseTestEntityDTO,并自动编译。
生成代码如下:
1// This codes are generated automatically. Do not modify!
2package com.example.annotation.dto;
3
4import com.example.annotation.entity.TestEntity;
5import java.lang.Integer;
6import java.lang.Long;
7import java.lang.String;
8import java.util.Date;
9import java.util.List;
10import lombok.AccessLevel;
11import lombok.Data;
12import lombok.Getter;
13import lombok.Setter;
14
15@Data
16public abstract class BaseTestEntityDTO {
17
18 @Setter(AccessLevel.PRIVATE)
19 @Getter(AccessLevel.PUBLIC)
20 private Integer age;
21
22 @Setter(AccessLevel.PRIVATE)
23 @Getter(AccessLevel.PUBLIC)
24 private Date birthday;
25
26 @Setter(AccessLevel.PRIVATE)
27 @Getter(AccessLevel.PUBLIC)
28 private Long id;
29
30 @Setter(AccessLevel.PRIVATE)
31 @Getter(AccessLevel.PUBLIC)
32 private String name;
33
34 protected BaseTestEntityDTO(TestEntity source) {
35 this.setAge(source.getAge());
36 this.setBirthday(source.getBirthday());
37 this.setId(source.getId());
38 this.setName(source.getName());
39 }
40}
此时便可以据此创建TestEntityDTO。
1@Data
2public class TestEntityDTO extends BaseTestEntityDTO{
3 private List<String> address;
4
5 private TestEntityDTO(TestEntity source) {
6 super(source);
7 this.address = Lists.newArrayList(source.getAddress());
8 }
9}
对于需要特殊处理的属性,进行特殊处理。如address的copy。
4.2. Spring基于反射的RequestMapping
Spring一直是java注解的使用大户,特别是RUNTIME级别、基于反射的注解,几乎无处不在。
4.2.1. 设计目标
模拟SpringMVC中的RequestMapping注解,收集path与method的映射关系。
4.2.2. 自定义注解
自定义RequestMappiing注解。
1@Target({ElementType.TYPE, ElementType.METHOD})
2@Retention(RetentionPolicy.RUNTIME)
3@Inherited
4@Documented
5public @interface RequestMapping {
6 String path() default "";
7}
Retention设置为RUNTIME,以通过反射获取注解信息。
4.2.3. 自定义注解处理器
1public class RequestMappingParser<T> {
2 private final Class<T> tClass;
3
4 public RequestMappingParser(Class<T> tClass){
5 this.tClass = tClass;
6 }
7
8 public Set<ParserResult> parse(){
9 RequestMapping cRequestMapping = this.tClass.getAnnotation(RequestMapping.class);
10 String rootPath = cRequestMapping != null ? cRequestMapping.path() : "";
11 if (!rootPath.startsWith("/")){
12 rootPath = "/" + rootPath;
13 }
14
15 Set<ParserResult> parserResults = Sets.newHashSet();
16 for (Method method : this.tClass.getMethods()){
17 RequestMapping mRequestMapping = method.getAnnotation(RequestMapping.class);
18 if (mRequestMapping == null){
19 continue;
20 }
21
22 String mPath = mRequestMapping.path();
23 if ("".equals(mPath)){
24 continue;
25 }
26 String path = null;
27 if (mPath.startsWith("/")){
28 path = rootPath + mPath;
29 }else {
30 path = rootPath + "/" + mPath;
31 }
32 parserResults.add(new ParserResult(path, method));
33 }
34 return parserResults;
35 }
36
37
38 @Data
39 static class ParserResult{
40 private final String path;
41 private final Method method;
42 }
43}
4.2.4. 使用注解
1@RequestMapping(path = "/root")
2public class RequestMappingObject {
3
4 @RequestMapping(path = "method")
5 public void method(){
6
7 }
8
9 @RequestMapping(path = "method1")
10 public void method1(){
11
12 }
13
14 @RequestMapping(path = "method2")
15 public void method2(){
16
17 }
18
19 @RequestMapping(path = "method3")
20 public void method3(){
21
22 }
23
24 @RequestMapping(path = "method4")
25 public void method4(){
26
27 }
28}
4.2.5. 处理结果
1public class RequestMappingTest {
2 @Test
3 public void print(){
4 new RequestMappingParser<>(RequestMappingObject.class)
5 .parse()
6 .forEach(parserResult -> {
7 System.out.print(parserResult.getPath());
8 System.out.print("-->");
9 System.out.print(parserResult.getMethod());
10 System.out.println();
11 });
12 }
13}
运行结果如下:
/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
原文始发于微信公众号(geekhalo):【Java 基础】– 注解
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/60374.html