java中如何自定義註解

飛馳的蝸牛發表於2019-10-17

作為一名開發人員,註解的的使用是最常見的了,比如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編譯器忽略特定的編譯警告)

至於註解內部詳細內容,大家可以點進去原始碼檢視。希望本篇文章對你有所幫助!

相關文章