作為一名開發人員,註解的的使用是最常見的了,比如Spring框架裡的業務層註解@Service、@Transaction,控制層用的@Controller、@Autowired,SpringBoot框架的啟動類註解@SpringBootApplication等等。那麼如何自定義註解呢?
一、什麼是註解
註解(Annotation)是後設資料的一種形式,從JDK5.0 引入,它能為程式碼提供一些相關資料,以便於在程式碼編譯或執行時使用。
二、註解的作用
Java中的註解可以修飾類、方法、變數、引數等。註解可以通過反射手段獲取其內容,在編譯器生成類檔案時,註解可以被嵌入到位元組碼中。當然JVM也可以保留註解的內容,在執行時動態,總結起來主要是以下幾個層面:
- 編譯器根據註解在編譯程式碼時行進行提示警告或錯誤資訊 例如我們在使用java.util包下的Date類時,呼叫了類中被@Deprecated標註的方法,IDE會在編譯時會有警告資訊
public static void main(String[] args) {
Date date = new Date();
//JDK原始碼中,Date類的getDay方法被@Deprecated註解標註,代表方法已過時
int day = date.getDay();
}
複製程式碼
- 編譯執行時時根據註解動態生成程式碼 例如在springboot框架中,我們實現一個Controller層方法的前置通知,通過使用@Aspect、@Before等註解即可,當然這些註解是框架封裝好的,遮蔽了底層的細節,但是AOP的原理,大家應該都很熟悉
@Slf4j
@Component
@Aspect
public class MyAspect {
@Before(value = "execution(public * com.test.controller.*.*(..))")
public void before(JoinPoint joinPoint) {
log.info("CLASS_METHOD:[{}]" , joinPoint.getSignature().getName());
}
}
複製程式碼
- 程式執行時使用註解進行動態賦值 比如通過使用@Value註解,將配置檔案引數賦給程式碼裡面變數,例如SpringBoot裡面整合RabbitMq時,賬戶密碼等配置資訊通過註解進行配置
@Configuration
public class RabbitMqConfig {
@Value("${spring.rabbitmq.host}")
private String rabbitMqHost;
@Value("${spring.rabbitmq.port}")
private String rabbitMqPort;
}
複製程式碼
三、自定義註解
首先我們先看下一個註解示例,下面是javafx.beans包下的@DefaultProperty註解 :
package javafx.beans;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies a property to which child elements will be added or set when an
* explicit property is not given.
*
* @since JavaFX 2.0
*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DefaultProperty {
/**
* The name of the default property.
*/
public String value();
}
複製程式碼
我們看到定義註解與定義一個class類相似,不過其中class關鍵字被替換為了@interface,註解裡宣告瞭一個String型別的value屬性,注意這種宣告屬性方式!稍後會詳細說明。同時還可以看到DefaultProperty註解被@Inherited、@Documented、@Retention(RetentionPolicy.RUNTIME)、@Target(ElementType.TYPE)這些註解所修飾,這就是我們需要知道另外一個概念——元註解(meta-annotations)。
1.元註解
元註解是我們在定義註解時需要用到的一些特殊含義的註解,可以說它們是對宣告註解時的註解。java語言為我們預設提供了以下元註解,在java.lang.annotation包下,我們來看下:
-
@Retention(RetentionPolicy.XX) 註解的保留域,表示註解的保留範圍,可選項有
- RetentionPolicy.SOURCE – 原始碼級別保留,編譯器編譯後該型別的註解就被丟棄掉了,生成的.class位元組碼檔案中,將不再存在該型別的註解.
- RetentionPolicy.CLASS – .class位元組碼檔案中保留,編譯器編譯後保留,JVM載入後丟棄掉,執行時無法獲取
- RetentionPolicy.RUNTIME – 執行時保留,在執行時,JVM使用反射,可以獲取註解屬性內容,絕大多數註解在定義是使用都是該級別
-
@Target( ElementType.XX) 指定該註解可以使用的地方,如類宣告、方法宣告,變數宣告等等, 在定義註解時,如果沒有使用Target指定,預設都可以使用。如果使用了Target指定使用的位置,那麼該註解只能在所指定的位置使用
- ElementType.ANNOTATION_TYPE 註解型別宣告
- ElementType.CONSTRUCTOR 構造方法
- ElementType.FIELD 欄位宣告(包括列舉常量)
- ElementType.LOCAL_VARIABLE 區域性變數宣告
- ElementType.METHOD 方法宣告
- ElementType.PACKAGE 包宣告
- ElementType.PARAMETER 方法的引數宣告
- ElementType.TYPE 類、介面(包括註解型別)或enum宣告
-
@Documented 表示在使用Javadoc工具生成文件時,包含此註解資訊
-
@Inherited 表示當前註解是可繼承的,父類中所使用的註解如果被@Inherited修飾,子類會繼承父類中對應的註解
2.註解屬性定義方式
明白了註解的外在定義形式,那麼我們就來看下註解內部的屬性的定義方式,Talk is cheap. Show me the code 多說無益,直接上程式碼
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
/**
*1、屬性的定義和介面中方法宣告類似,訪問修飾符預設是public,可省略,注意屬性名後面跟了()
*/
String name();
/**
* 2、可以通過default為屬性指定預設值,在註解使用時,可以為之賦值, 也可以不賦值
* 當然如果不通過default為屬性指定預設值,在註解使用必須使用該屬性並且為之賦值
*/
int age() default 1;
/**
* 3、註解中的屬性value比較特殊,如果使用註解時僅為該屬性賦值,"value="可以省略掉,
* 但是如果和其他屬性同時賦值,“value=”則不能省略,這個特性和value的屬性型別無關
*/
String value() default "";
/**
* 4.註解中屬性的型別可以是基本資料型別及其陣列、類、列舉、註解
*/
boolean sex() default true;
}
複製程式碼
四、通過反射獲取註解屬性值
在上面程式碼中,我們自定義了@MyAnnotation註解,並且指明@Target({ElementType.METHOD})表明此註解只能用在方法宣告上, @Retention(RetentionPolicy.RUNTIME)指定其保留到程式碼執行時,所以我們可以通過反射獲取MyAnnotation的屬性值。下面我們定義了一個Person類,並在其中定義了一個sayHello方法,在方法宣告上,我們使用@MyAnnotation註解,我們將使用反射呼叫sayHello方法,並且使用MyAnnotation註解中的屬性值
package com.test.annotation;
import com.test.enu.Color;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Person {
@MyAnnotation(name="韓梅梅",age = 30,sex = true,clothes = Color.YELLOW)
public void sayHello(String name){
System.out.println("Hello!" + name);
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
Person person = new Person();
//獲取Person類的Class物件
Class<? extends Person> personClass = person.getClass();
//獲取類中宣告的方法列表
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//判斷當前方法是否含有MyAnnotation註解
if(method.isAnnotationPresent(MyAnnotation.class)){
//獲取MyAnnotation型別註解
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
//反射呼叫方法,並傳遞註解name屬性值為引數
Object invoke = method.invoke(person,myAnnotation.name());
//列印註解中定義的各個型別的值
System.out.println(myAnnotation);
System.out.println(myAnnotation.name()+","+myAnnotation.sex()+","+myAnnotation.age());
}
}
}
}
複製程式碼
此時的IDE控制檯輸出,說明我們通過反射在執行時獲取到了@MyAnnotation註解的值
Hello!韓梅梅
韓梅梅,true,30
@com.test.annotation.MyAnnotation(value=, age=30, sex=true, name=韓梅梅, clothes=YELLOW)
複製程式碼
五、JDK內部自帶註解
JDK自帶了一些原先定義好的註解,我們可以直接使用
- @Override 表示當前方法覆蓋了父類的方法
- @Deprecation 表示方法已經過時,方法上有橫線,使用時會有警告。
- @SuppviseWarnings 表示關閉一些警告資訊(通知java編譯器忽略特定的編譯警告)
至於註解內部詳細內容,大家可以點進去原始碼檢視。希望本篇文章對你有所幫助!