Java自定義註解Annotation詳解

一:簡介

開發中經常使用到註解,在項目中也偶爾會見到過自定義註解,今天就來探討一下這個註解是什麼鬼,以及註解的應用場景和如何自定義註解。

下面列舉開發中常見的註解

  • @Override:用於標識該方法繼承自超類, 當父類的方法被刪除或修改了,編譯器會提示錯誤信息(我們最經常看到的toString()方法上總能看到這貨)

  • @Deprecated:表示該類或者該方法已經不推薦使用,已經過期了,如果用戶還是要使用,會生成編譯的警告

  • @SuppressWarnings:用於忽略的編譯器警告信息

  • Junit測試:@Test

  • Spring的一些註解:@Controller、@RequestMapping、@RequestParam、@ResponseBody、@Service、@Component、@Repository、@Resource、@Autowire

  • Java驗證的註解:@NotNull、@Email

下面看一下註解Override.java的廬山真面目

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

二:註解基本知識

1. 註解數據類型

註解是寫在.java文件中,使用@interface作爲關鍵字, 所以註解也是Java的一種數據類型,從廣泛的定義來說,Class、Interface、Enum、Annotation都屬於Class類型。

2. 元註解

在創建註解的時候,需要使用一些註解來描述自己創建的註解,就是寫在@interface上面的那些註解,這些註解被稱爲元註解,如在Override中看到的@Target、@Retention等。下面列出一些元註解

  • @Documented: 用於標記在生成javadoc時是否將註解包含進去,可以看到這個註解和@Override一樣,註解中空空如也,什麼東西都沒有

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    
  • @Target:用於定義註解可以在什麼地方使用,默認可以在任何地方使用,也可以指定使用的範圍,開發中將註解用在類上(如@Controller)、字段上(如@Autowire)、方法上(如@RequestMapping)、方法的參數上(如@RequestParam)等比較常見。

    • TYPE : 類、接口或enum聲明
    • FIELD: 域(屬性)聲明
    • METHOD: 方法聲明
    • PARAMETER: 參數聲明
    • CONSTRUCTOR: 構造方法聲明
    • LOCAL_VARIABLE:局部變量聲明
    • ANNOTATION_TYPE:註釋類型聲明
    • PACKAGE: 包聲明

    Target.java

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        /**
         * Returns an array of the kinds of elements an annotation type
         * can be applied to.
         * @return an array of the kinds of elements an annotation type
         * can be applied to
         */
        ElementType[] value();
    }
    
    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
        /** Field declaration (includes enum constants) */
        FIELD,
        /** Method declaration */
        METHOD,
        /** Formal parameter declaration */
        PARAMETER,
        /** Constructor declaration */
        CONSTRUCTOR,
        /** Local variable declaration */
        LOCAL_VARIABLE,
        /** Annotation type declaration */
        ANNOTATION_TYPE,
        /** Package declaration */
        PACKAGE,
        /** Type parameter declaration */
        TYPE_PARAMETER,
        /** Use of a type */
        TYPE_USE
        }
    
  • @Inherited:允許子類繼承父類中的註解,可以通過反射獲取到父類的註解

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }
    
  • @Constraint:用於校驗屬性值是否合法

    @Documented
    @Target({ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Constraint {
        Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
    }
    
  • @Retention:註解的聲明週期,用於定義註解的存活階段,可以存活在源碼級別、編譯級別(字節碼級別)、運行時級別

    • SOURCE:源碼級別,註解只存在源碼中,一般用於和編譯器交互,用於檢測代碼。如@Override, @SuppressWarings。

    • CLASS:字節碼級別,註解存在於源碼和字節碼文件中,主要用於編譯時生成額外的文件,如XML,Java文件等,但運行時無法獲得。 如mybatis生成實體和映射文件,這個級別需要添加JVM加載時候的代理(javaagent),使用代理來動態修改字節碼文件。

    • RUNTIME:運行時級別,註解存在於源碼、字節碼、java虛擬機中,主要用於運行時,可以使用反射獲取相關的信息。

      @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.ANNOTATION_TYPE)
      public @interface Retention {
          /**
           * Returns the retention policy.
           * @return the retention policy
           */
          RetentionPolicy value();
      }
      

3. 註解的內容

在上面的註解源碼中可以看到有的註解中沒有任何內容,有的註解的有內容,看似像方法。

註解的內容的語法格式: 數據類型 屬性名() default 默認值,數據類型用於描述屬性的數據類型,默認值是說當沒有給屬性賦值時使用默認值,一般String使用空字符串""作爲默認值,數組一般使用空數組{ }作爲默認值.

下面看一下SpringMVC中的RequestMapping的註解的聲明

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};
    RequestMethod[] method() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}

使用SpringMVC中的RequestMapping註解

@RequestMapping(value = "/list", 
				method = RequestMethod.POST, 
				produces = {"application/json;charset=UTF-8;"})
public String list(){
}

4. 註解的使用場景

可以通過註解的聲明週期來分析註解的使用場景:

  • SOURCE源碼級別:給編譯器使用,如@Override、@Deprecated 等, 這部分開發者應該使用的場景不多
  • CLASS:字節碼級別,這部分也很少見到
  • RUNTIME:運行時級別,這個是最多的,幾乎開發者使用到的註解都是運行時級別,運行時註解常用的有以下幾種情況
    • 註解中沒有任何屬性的,空的註解,這部分註解通常起到一個標註的作用,如@Test、@Before、@After,通過獲取這些標記註解在邏輯上做一些特殊的處理
    • 可以使用約束註解@Constraint來對屬性值進行校驗,如@Email, @NotNull等
    • 可以通過在註解中使用屬性來配置一些參數,然後可以使用反射獲取這些參數,這些註解沒有其他特殊的功能,只是簡單的代替xml配置的方式來配置一些參數。使用註解來配置參數這在Spring boot中得到了熱捧,如@Configuration

關於配置方式xml vs annotation, 一般使用xml配置一些和業務關係不太緊密的配置,使用註解配置一些和業務密切相關的參數。


三:註解和反射基本API

// 獲取某個類型的註解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
// 獲取所有註解(包括父類中被Inherited修飾的註解)
public Annotation[] getAnnotations(); 
// 獲取聲明的註解(但是不包括父類中被Inherited修飾的註解)
public Annotation[] getDeclaredAnnotations();
// 判斷某個對象上是否被某個註解進行標註
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 獲取某個類聲明的所有字段
public Field[] getDeclaredFields() throws SecurityException;
// 獲取某個方法
public Method getMethod(String name, Class<?>... parameterTypes);

四:自定義註解

使用自定義註解+攔截器或者是AOP等可以進行權限的控制。

下面通過定義一個註解用來限制當用戶訪問接口時必須要登錄的示例

步驟一:定義註解
RequiresLogin.java

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresLogin {
}

步驟二:使用註解

@Controller
@RequestMapping("/user")
public class UserController {
	@RequiresLogin
	@RequestMapping(value = "/list", produces = {"application/json;charset=UTF-8;"})
	public String getUserList(){
			System.out.println("--------------");
			return "[{'id': 1, 'username':'zhangsan'}]";
	}
}

步驟三:使用AOP進行攔截,解析註解

public class LoginAdvices {
    public void before(JoinPoint joinPoint) throws Exception{
        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        System.out.println(target + "-------" + methodName);
        Method method = target.getClass().getMethod(methodName);
        boolean annotationPresent = method.isAnnotationPresent(RequiresLogin.class);
        if (annotationPresent) {
            // 用戶必須登錄
            boolean isLogin = false;
            if (!isLogin) {
                throw new Exception("訪問該接口必須先登錄");
            } else {
                System.out.println("已登錄...");
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章