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用來定義該注解在哪個級別上可用。

@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的注解處理器開發,主要包括兩個步驟:

  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<String> getSupportedAnnotationTypes() 返回此 Processor 支持的注釋類型的名稱
  3. SourceVersion getSupportedSourceVersion() 返回此注釋 Processor 支持的最新的源版本
  4. Set<String> 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<String> getSupportedAnnotationTypes() 如果 processor類是使用SupportedAnnotationTypes注釋的,則返回一個不可修改的集合,該集合具有與注釋相的字符串集
  3. SourceVersion getSupportedSourceVersion()如果 processor類是使用SupportedSourceVersion注釋的,則返回注釋中的源版本
  4. Set<String> 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文件內容:

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 extends Annotation> T getAnnotation(Class<T> 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。

@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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容