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
在運行時保留注解,因此通過反射機制可以拿到注解信息
- SOURCE
- @Document
將此注解包含到JavaDoc中 - @Inherited
允許子類繼承父類的注解
1.2. 自定義注解
1.2.1. 定義注解
下面是一個自動生成DTO的注解,可見注解的定義看起來很像接口定義,事實上,與接口定義一樣,注解也會編譯成class文件。
除@符外注解定義就像一個空接口,其中@Target和@Retention尤為重要,@Target用于定義注解將用于什么地方,@Retention用來定義該注解在哪個級別上可用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTO {
String pkgName();
}
此注解,使用在類型之上,作用于SOURCE,允許子類繼承,并將其包含到Javadoc中。
@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等)的使用一模一樣,一個比較大的區別是注解上可以設置注解元素,以便注解處理器使用。
@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的注解處理器開發,主要包括兩個步驟:
- 實現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<String> getSupportedAnnotationTypes() 返回此 Processor 支持的注釋類型的名稱
- SourceVersion getSupportedSourceVersion() 返回此注釋 Processor 支持的最新的源版本
- Set<String> getSupportedOptions() 返回此 Processor 識別的選項
- boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 處理先前產生的類型元素上的注釋類型集,并返回這些注釋是否由此 Processor處理
- Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 向工具框架返回某一注釋的建議 completion 迭代
AbstractProcessor
一般情況下,我們很少直接實現Processor,而是繼承AbstractProcessor,并在它基礎上進行擴展。
AbstractProcessor對Processor接口進行了擴展,以方便擴展。
AbstractProcessor核心方法:
- void init(ProcessingEnvironment processingEnv)
環境初始化方法,將processingEnv字段設置為 processingEnv 參數的值 - Set<String> getSupportedAnnotationTypes() 如果 processor類是使用SupportedAnnotationTypes注釋的,則返回一個不可修改的集合,該集合具有與注釋相的字符串集
- SourceVersion getSupportedSourceVersion()如果 processor類是使用SupportedSourceVersion注釋的,則返回注釋中的源版本
- Set<String> getSupportedOptions() 如果 processor類是使用SupportedOptions注釋的,則返回一個不可修改的集合,該集合具有與注釋相的字符串集
- Iterable<? extends Completion> 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<String,String> 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文件內容:
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信息:
- <T extends Annotation> T getAnnotation(Class<T> annotationClass)返回程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null
- Annotation[] getAnnotations() 返回該程序元素上存在的所有注解
- boolean is AnnotationPresent(Class<?extends Annotation> 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。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTO {
String pkgName();
}
標記于Domain類上,用于說明該類需要生成BaseDomainDTO
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTOIgnore {
}
注解與Domain中的字段上,在生成BaseDomainDTO時,忽略該字段
4.1.3. 自定義注解處理器
自定義注解處理器,在編譯過程中,讀取注解信息,并完成BaseDomainDTO的代碼生成。
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,對通用行為進行封裝。
@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中添加注解即可。
/**
* 設置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,并自動編譯。
生成代碼如下:
// 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。
@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注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RequestMapping {
String path() default "";
}
Retention設置為RUNTIME,以通過反射獲取注解信息。
4.2.3. 自定義注解處理器
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. 使用注解
@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. 處理結果
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