1. 程式人生 > >Java中自定義註解

Java中自定義註解

隨著 java註解 can 中文名 fault rev 相同 val source

前言

隨著springboot的流行,以前基於XML的spring配置用的越來越少,JavaConfig形式使用的越來越多,類似於:

@Configuration
public class AppConfig {
 
  @Bean(name="helloBean")
  public HelloWorld helloWorld() {
     return new HelloWorldImpl();
  }
}

可以看出更多的是基於註解(Annotation)實現的,包括springboot的入口類**Application。

@Configuration
@ComponentScan(
"com.alibaba.trade") @EnableAutoConfiguration//(exclude = {PageHelperAutoConfiguration.class}) @ServletComponentScan @EnableTransactionManagement @EnableDiscoveryClient @EnableWebMvc @MapperScan("com.alibaba.trade.shared.mapper") public class TradeApplication extends SpringBootServletInitializer {
public static void main(String[] args) { SpringApplication.run(TradeApplication.class, args); } }

Java註解不僅讓我們減少了項目中XML文件,方便了維護,同時也使我們代碼更簡潔。那麽項目中我們如何閱讀註解以及如何創造自己的註解呢?

註解說明

Java註解又稱Java標註,是Java語言5.0版本開始支持加入源代碼的特殊語法元數據。為我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便的使用這些數據。
Java語言中的類、方法、變量、參數和包等都可以被標註。和Javadoc不同,Java標註可以通過反射獲取註解內容。在編譯器生成類文件時,註解可以被嵌入到字節碼中。Java虛擬機可以保留註解內容,在運行時可以獲取到註解內容。

註解本身沒有具體的功能,它相當於一個標註,而這個標註具體的作用和意義需要我們自己實現。一般都是通過反射來獲取註解屬性再實現具體業務功能。

內置註解

Java 定義了一套註解,共有 7 個,3 個在 java.lang 中,剩下 4 個在 java.lang.annotation 中。
1、作用在代碼的註解是

  • @Override - 檢查該方法是否是重載方法。如果發現其父類,或者是引用的接口中並沒有該方法時,會報編譯錯誤。
  • @Deprecated - 標記過時方法。如果使用該方法,會報編譯警告。
  • @SuppressWarnings - 指示編譯器去忽略註解中聲明的警告。

2、作用在其他註解的註解(或者說元註解)是:

  • @Retention - 標識這個註解怎麽保存,是只在代碼中,還是編入class文件中,或者是在運行時可以通過反射訪問。
  • @Documented - 標記這些註解是否包含在用戶文檔中。
  • @Target - 標記這個註解應該是哪種 Java 成員。
  • @Inherited - 標記這個註解是繼承於哪個註解類(默認 註解並沒有繼承於任何子類)

3、從 Java 7 開始,額外添加了 3 個註解:

  • @SafeVarargs - Java 7 開始支持,忽略任何使用參數為泛型變量的方法或構造函數調用產生的警告。
  • @FunctionalInterface - Java 8 開始支持,標識一個匿名函數或函數式接口。
  • @Repeatable - Java 8 開始支持,標識某註解可以在同一個聲明上使用多次。

元註解

1、@Retention

@Retention annotation指定標記註釋的存儲方式:

  • RetentionPolicy.SOURCE - 標記的註釋僅保留在源級別中,並由編譯器忽略。
  • RetentionPolicy.CLASS - 標記的註釋在編譯時由編譯器保留,但Java虛擬機(JVM)會忽略。
  • RetentionPolicy.RUNTIME - 標記的註釋由JVM保留,因此運行時環境可以使用它。

2、@Documented
@Documented 註釋表明,無論何時使用指定的註釋,都應使用Javadoc工具記錄這些元素。(默認情況下,註釋不包含在Javadoc中。)有關更多信息,請參閱 Javadoc工具頁面。

3、@Target

@Target 註釋標記另一個註釋,以限制可以應用註釋的Java元素類型。目標註釋指定以下元素類型之一作為其值

  • ElementType.TYPE 可以應用於類的任何元素。
  • ElementType.FIELD 可以應用於字段或屬性。
  • ElementType.METHOD 可以應用於方法級註釋。
  • ElementType.PARAMETER 可以應用於方法的參數。
  • ElementType.CONSTRUCTOR 可以應用於構造函數。
  • ElementType.LOCAL_VARIABLE 可以應用於局部變量。
  • ElementType.ANNOTATION_TYPE 可以應用於註釋類型。
  • ElementType.PACKAGE 可以應用於包聲明。
  • ElementType.TYPE_PARAMETER
  • ElementType.TYPE_USE

4、@Inherited
@Inherited 註釋表明註釋類型可以從超類繼承。當用戶查詢註釋類型並且該類沒有此類型的註釋時,將查詢類的超類以獲取註釋類型(默認情況下不是這樣)。此註釋僅適用於類聲明。

5、@Repeatable
Repeatable Java SE 8中引入的,@Repeatable註釋表明標記的註釋可以多次應用於相同的聲明或類型使用(即可以重復在同一個類、方法、屬性等上使用)。

自定義註解

Java中自定義註解和創建一個接口相似,自定義註解的格式是以@interface為標誌的。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
 
    /**
    * default extension name
    */
    String value() default "";
 
}

我們知道java.lang.annotation包中有一個Annotation的接口,它是所有註解類型擴展的公共接口。那我們是否可以直接通過實現該接口來實現自定義註解呢?

import java.lang.annotation.Annotation;
 
public class MyAnnotation implements Annotation {
 
    @Override
    public Class<? extends Annotation> annotationType() {
    return null;
    }
}

發現Annotation接口中只有一個annotationType的方法,而且通過Annotation源碼的註釋我們可以發現答案是不能。

技術分享圖片

漢譯即為:Annotaion被所有註解類型繼承,但是要註意:手動擴展繼承此接口的接口不會定義註解類型。另請註意,此接口本身不定義註解類型。

使用場景

自定義註解的使用場景很多,我們在造輪子寫框架的過程經常會使用到,例如我最近就遇到了一個業務場景:像一些編輯業務信息的接口,產品要求信息編輯後的新舊值對比,對比的業務功能,我們的實現方式是拿到前端填寫的Form表單(新值)和數據庫中查詢出來的Dto(舊值)通過反射技術獲取到相同屬性字段名,再比較屬性值就可以得出新舊值。得到值之後我們也知道該字段的Dto中的字段名,但是如何將比較得到的新舊值字段的中文名返回給前端呢?例如:

public class Stedent {
 
    private String name;
 
    private int age;
 
    private String sex;
 
    //省略setter,getter
}

比較後我們的結果是 name : “xiaoming “-> “daming”,age : 24 -> 26。但是我們不能直接將name和age返回給前端,他們需要的格式是:姓名: “xiaoming “-> “daming”,年齡 : 24 -> 26。這時候就可以考慮自定義一個註解@FieldName,

@Deprecated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldName {

    String value() default "";

}

然後將該註解加在屬性字段上面

public class Student {
 
    @FieldName(value = "姓名")
    private String name;
 
    @FieldName(value = "年齡")
    private int age;
 
    @FieldName(value = "性別")
    private String sex;
 
    //省略setter,getter
}

之後就可以通過反射獲取該字段中文名。

// 如果 oldField 屬性值與 newField 屬性值的內容不相同
if (!isEmpty(newValue)) {
    Map<String, Object> map = new HashMap<>();
    String newFieldName = newField.getName();
    if (newField.isAnnotationPresent(ApiModelProperty.class)) {
        ApiModelProperty apiModelPropertyAnno = newField.getAnnotation(ApiModelProperty.class);
        newFieldName = apiModelPropertyAnno.value();
    else if (newField.isAnnotationPresent(FieldName.class)) {
        FieldName fieldNameAnno = newField.getAnnotation(FieldName.class);
        newFieldName = fieldNameAnno.name();
    }
        map.put(FIELD_NAME, newFieldName);
        map.put(OLD_VALUE, oldValue);
        map.put(NEW_VALUE, newValue);
        list.add(map);
}

Java中自定義註解