Java自定義註解
前言
隨著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 @Documented @Target @Inherited
3、從 Java 7 開始,額外添加了 3 個註解:
@SafeVarargs @FunctionalInterface @Repeatable
元註解
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的方法,而且通過原始碼的註釋我們可以發現答案是不能。

漢譯即為: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); }