高級工程師-Java注解


高級工程師-Java注解

前言

代碼,就是我們身為程序員的名片。

簡潔,優雅,統一,是我們的追求。

優秀的代碼,會給瀏覽者一種藝術的美感。如DL大神的JUC包,感興趣的小伙伴,可以研究一下。

那么日常中,各位看到的優秀代碼,有着哪些特點呢?充分利用的工具類(lang3,lombok,Validation等等),完善的注解,統一的代碼規范等等。還有的,就是Java語言的諸多高級特性(lambda,stream,io等)。

Java語言中,有三個特性,是高級工程師不可或缺的:

  • 注解
  • 反射
  • 泛型

如果代碼中,存在這些東西,那么即使應用得還不夠合理,也能夠從側面證明這位程序員的技術追求。

這三點是初級工程師很難掌握的,因為缺乏了解與需求(或者說想不到對應的需求)。而高級工程師為了給出更加具有通用性,業務無侵入的代碼,就常常需要與這些特性打交道。

在不斷積累后的今天,我覺得我可以嘗試寫一寫自己對這些特性的認識了。

今天就從注解開始,闡述我對高級工程師的一些編碼認識。

簡介

我發現很多小伙伴總是在喜歡記憶一些注解的功能,比如表示非空的@NotNull等。

這里,我要從功能與原理角度說明兩點:

  • 功能:注解是一種“增強型”的注釋。只不過相對於只能給人看的注釋,注解可以給電腦(JVM,程序等)看。
  • 原理:注解的底層是Annotation接口的繼承者。只不過相對於日常使用的接口,注解需要使用@interface,但是編譯的結果依舊是接口繼承(如TestAnnotation extend Annotation)。

請大家牢記上面兩點,這是有關注解認識的絕對核心

只要大家抓住這兩個角度去認識注解,那么很快就可以成為注解達人。后續很多闡述都會從這兩個角度,去為大家解釋。如為什么人們常說注解是無法繼承的,為什么需要元注解等等。

注解的目錄結構

在這里插入圖片描述

其實可以看到,JDK中有關注解的內容很少,非常適合作為三大特性的入門啊。因為注解的實現基礎是存在於JVM中的,JDK只是提供了對應的工具。

Annotation接口

上面提到注解的底層是接口,這里以圖為證。

在這里插入圖片描述

注意,仔細看這個接口的注釋。注釋中明確提出,雖然注解的本質是接口。但是直接引用Annotation接口,是無法實現注解功能的。

元注解

簡介

通俗來說,元注解就是注解的注解。

首先元注解,是Java自帶的預置注解。從這個角度,需要與@XXX修飾的自定義注解進行區分。

站在功能上來說,元注解就是專門修飾注解的“注釋”,用來告訴編譯器,虛擬機,相關的信息(如運行時間,目標對象)。

站在原理上來說,元注解也是注解,也是使用了@interface(底層依舊是繼承Annotation接口)。

不過注解,在底層實現已經繼承Annotation接口,那么就無法通過繼承接口的方式(Java不支持多重繼承),來保存元注解的信息(尤其這個信息往往不止一類)。那么注解的元注解信息是如何保存,並交給計算機的呢?答案就是通過RuntimeVisibleAnnotations進行相關信息的保存的。以下就是對DynamicPropertyVerification注解反編譯的結果,重點在於反編譯結果的最后一段。


	Classfile /D:/IDEA_Project/IdeaProjects/learning/demo/target/classes/tech/jarry/learning/demo/common/anno/DynamicPropertyVerification.class
	  Last modified Apr 12, 2020; size 899 bytes
	  MD5 checksum 72657e8b89f0de070bf7085b0dd975da
	  Compiled from "DynamicPropertyVerification.java"
	public interface tech.jarry.learning.demo.common.anno.DynamicPropertyVerification extends java.lang.annotation.Annotation
	  minor version: 0
	  major version: 52
	  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
	Constant pool:
	   #1 = Class              #28            // tech/jarry/learning/demo/common/anno/DynamicPropertyVerification
	   #2 = Class              #29            // java/lang/Object
	   #3 = Class              #30            // java/lang/annotation/Annotation
	   #4 = Utf8               message
	   #5 = Utf8               ()Ljava/lang/String;
	   #6 = Utf8               AnnotationDefault
	   #7 = Utf8               property verification fail
	   #8 = Utf8               groups
	   #9 = Utf8               ()[Ljava/lang/Class;
	  #10 = Utf8               Signature
	  #11 = Utf8               ()[Ljava/lang/Class<*>;
	  #12 = Utf8               payload
	  #13 = Utf8               ()[Ljava/lang/Class<+Ljavax/validation/Payload;>;
	  #14 = Utf8               SourceFile
	  #15 = Utf8               DynamicPropertyVerification.java
	  #16 = Utf8               RuntimeVisibleAnnotations
	  #17 = Utf8               Ljava/lang/annotation/Documented;
	  #18 = Utf8               Ljava/lang/annotation/Target;
	  #19 = Utf8               value
	  #20 = Utf8               Ljava/lang/annotation/ElementType;
	  #21 = Utf8               FIELD
	  #22 = Utf8               Ljava/lang/annotation/Retention;
	  #23 = Utf8               Ljava/lang/annotation/RetentionPolicy;
	  #24 = Utf8               SOURCE
	  #25 = Utf8               Ljavax/validation/Constraint;
	  #26 = Utf8               validatedBy
	  #27 = Utf8               Ltech/jarry/learning/demo/common/anno/DynamicPropertyVerificationValidator;
	  #28 = Utf8               tech/jarry/learning/demo/common/anno/DynamicPropertyVerification
	  #29 = Utf8               java/lang/Object
	  #30 = Utf8               java/lang/annotation/Annotation
	{
	  public abstract java.lang.String message();
	    descriptor: ()Ljava/lang/String;
	    flags: ACC_PUBLIC, ACC_ABSTRACT
	    AnnotationDefault:
	      default_value: s#7
	  public abstract java.lang.Class<?>[] groups();
	    descriptor: ()[Ljava/lang/Class;
	    flags: ACC_PUBLIC, ACC_ABSTRACT
	    AnnotationDefault:
	      default_value: []Signature: #11       // ()[Ljava/lang/Class<*>;
	
	  public abstract java.lang.Class<? extends javax.validation.Payload>[] payload();
	    descriptor: ()[Ljava/lang/Class;
	    flags: ACC_PUBLIC, ACC_ABSTRACT
	    AnnotationDefault:
	      default_value: []Signature: #13       // ()[Ljava/lang/Class<+Ljavax/validation/Payload;>;
	}
	SourceFile: "DynamicPropertyVerification.java"
	RuntimeVisibleAnnotations:
	  0: #17()
	  1: #18(#19=[e#20.#21])
	  2: #22(#19=e#23.#24)
	  3: #25(#26=[c#27])

最后一段,通過RuntimeVisibleAnnotations,保存了所需要的元注解信息。

如果對JVM底層原理有了解的小伙伴,應該對RuntimeVisibleAnnotations不陌生。不了解的小伙伴,可以查看Class RuntimeVisibleAnnotations

預置注解

元注解

元注解是Java自帶的,主要分為:

  • @Rentention:表示目標注解的保持策略。其value為RetentionPolicy。如果目標注解沒有使用該注解,則默認使用RetentionPolicy.CLASS
  • @Target:表示目標注解的應用目標類型。其value為ElementType。如果目標注解沒有使用該注解,則目標注解可以用於除了TYPE_PARAMETER和TYPE_USE以外的任何地方(這兩個類型都是Java8新添加的)。
  • @Documented:表示目標注解可以出現在JavaDoc中。
  • @Repeatable:表示目標注解可以在同一位置,重復使用。
  • @Inherited:表示目標注解可以隨着所修飾的類的繼承關系,被子類繼承。

@Retention

源碼:


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

通過RetentionPolicy枚舉表示目標注解的保持策略。


	public enum RetentionPolicy {
	    /**
	     * 目標注解會在編譯期丟失
	     */
	    SOURCE,
	
	    /**
	     * 默認行為。雖然目標注解會通過編譯,保存至.class文件中,但是JVM不會在運行時識別該注解。
	     */
	    CLASS,
	
	    /**
	     * 常用行為。目標注解會保存至.class文件中,JVM會在運行時識別,並記錄該注解。所以可以通過反射獲取對應的信息。
	     * 詳見 java.lang.reflect.AnnotatedElement
	     */
	    RUNTIME
	}

為了便於大家理解,這里再舉一些例子。這里挑選一些Java自帶的,不用大家再去自己寫demo,增加認知負荷:

  • @Retention(RetentionPolicy.SOURCE):如@Override注解,由於該注解只是用於進行代碼檢測,所以只要存在於源碼中即可,故選擇RetentionPolicy.SOURCE。類似的還有@SuppressWarnings注解等。
  • @Retention(RetentionPolicy.CLASS):涉及注解處理器,所以實例很少。可以查看自定義注解之編譯時注解(RetentionPolicy.CLASS)(一)
  • @Retention(RetentionPolicy.RUNTIME):如@Deprecated,由於該注解需要在運行時提示用戶注解修飾的方法,類等已經過時,所以需要JVM中有對應“注釋”信息,故采用RetentionPolicy.RUNTIME。類似的還有@Repeatable等。

@Target


	@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();
	}

通過ElementType枚舉表示目標注解的應用目標類型。


	public enum ElementType {
	    /** 類,接口(包括注解,即Annotation接口),或者枚舉類型 */
	    TYPE,
	
	    /** 屬性 (包括枚舉常量,枚舉常量示例:Retention.SOURCE) */
	    FIELD,
	
	    /** 方法 */
	    METHOD,
	
	    /** 形參(形式參數) */
	    PARAMETER,
	
	    /** 構造器 */
	    CONSTRUCTOR,
	
	    /** 本地變量 */
	    LOCAL_VARIABLE,
	
	    /** 注解類型 */
	    ANNOTATION_TYPE,
	
	    /** 包 */
	    PACKAGE,
	
	    /**
	     * 類型參數(針對數據類型)
	     * @since 1.8
	     */
	    TYPE_PARAMETER,
	
	    /**
	     * 類型(功能域包含PARAMETER與TYPE_PARAMETER)
	     * @since 1.8
	     */
	    TYPE_USE
	}

這里不會一一舉例,只會點出重點:

  • TYPE_PARAMETER與TYPE_USE是Java8新增加的。所以使用Java7的小伙伴要注意。
  • ElementType.TYPE涵蓋范圍很廣泛,在不知用哪個時,可以先用這個。

@Documented

默認情況下,注解是不出現在 javadoc 中的。通過給目標注解加上 @Documented 元注解,能使目標注解出現在 javadoc 中。

在這里插入圖片描述

從源碼可以看出,@Documented是一個沒有任何成員的標記注解。

@Repeatable

在這里插入圖片描述

@Repeatable注解的使用,引用一個不錯的demo


	package com.zejian.annotationdemo;
	import java.lang.annotation.*;/**
	* Created by zejian on 2017/5/20.
	*/
	@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
	@Retention(RetentionPolicy.RUNTIME)
	@Repeatable(FilterPaths.class)
	public @interface FilterPath {
	   String  value();
	}
	
	@Target(ElementType.TYPE)
	@Retention(RetentionPolicy.RUNTIME)
	@interface FilterPaths {
	   FilterPath[] value();
	}
	
	@FilterPath("/web/update")
	@FilterPath("/web/add")
	@FilterPath("/web/delete")

上述代碼,其實分為兩個部分:

  • 使用@Repeatable注解,使得其修飾的@FilterPath,可以在目標上重復標記(便於設置不同的成員變量)。
  • 通過@FilterPaths注解(包含成員變量-FilterPath[] value ()),將@FilterPath集中到@FilterPaths中,便於后續邏輯處理。

@Inherited

@Inherited同樣是只能修飾注解的元注解,它所標注的目標注解具有繼承性。

這里解釋一下這個繼承性,這並不是注解間的繼承。而是指目標注解可以隨着類的繼承,而被子類繼承。簡單說,就是目標注解修飾的類,其后代類也會被該注解標注(可以通過getAnnotation方法獲取)。

在這里插入圖片描述

這里不再贅述,感興趣的小伙伴,可以查看Java 注解(Annotation)中的相關示例。

功能注解

Java預置的功能注解,主要分為:

  • @Override:該注解修飾的目標方法,必須是重寫基類方法,或實現對應接口方法,否則編譯器會報錯。
  • @Deprecated:該注解修飾的目標,表示已經過時,不推薦使用。編碼時,使用該注解的目標,會有划線提示。
  • @SuppressWarnings:該注解修飾的目標,將會忽略某些異常(由注解的value指定),從而通過編譯器編譯。
  • @SafeVarargs:該注解修飾的構造函數(只能修飾構造函數),將會忽略可變參數帶來的警告。該注解於Java7引入。
  • @FunctionalInterface:該注解修飾的接口,為函數式接口。如java.util.function下的Consumer 接口,作為一個函數式接口,被該注解修飾(函數式接口不一定有該注解修飾,但被該注解修飾的接口,一定是函數式接口)。

自定義注解

到了這里,大家應該對注解不再陌生了。
而在日常開發中,我們常常需要自定義開發一些注解。
自定義注解分為以下步驟:

  1. [必選] 使用@interface來構建自定義注解。一般在創建自定義注解的同時,就達成了該要求。
  2. [可選] 使用@Target元注解。通過該注解,確定自定義注解的作用目標類型。注意:如果目標注解沒有使用該注解,則目標注解可以用於除了TYPE_PARAMETER和TYPE_USE以外的任何地方(這兩個類型都是Java8新添加的)。
  3. [可選] 使用@Retention元注解。通過該注解,明確自定義注解的生命周期,或者說自定義注解作用域。如果目標注解沒有使用該注解,則默認使用RetentionPolicy.CLASS
  4. [可選] 添加成員變量。格式為“long value() default 1000L;”,與Java8的接口成員變量非常類似。注意:注解的成員變量只能采用無參方法表示。並且注解的成員變量,只能采用基本數據類型(char,boolean,byte、short、int、long、float、double)和String、Enum、Class、annotations數據類型,以及這一些類型的數組。
  5. [可選] 使用自定義注解。自定義注解的使用領域很多,主要分為兩個方向:
    • 利用已有框架,不需要自己實現相關邏輯,自定義注解多作為標記注解。如配合SpringBoot的注解,形成自己的注解(相關的邏輯由SpringBoot自己處理)
    • 利用已有框架,需要自己實現部分邏輯(不涉及反射),但需要關聯已有框架,並實現對應接口。如Validation框架的自定義校驗注解,感興趣的小伙伴,可以查看我之前寫的Validation框架的應用
    • 可選擇已有框架,需要自己實現諸多邏輯。如在AOP中,我們常常需要通過反射,獲取自定義注解的信息(如參數等),或者自定義注解修飾的目標的信息(如參數,方法名等)。這部分,我會在后續的反射部分詳細說明。

總結

簡單總結一下,本文主要描述了:

  1. 注解是什么:增強型的注釋,本質是接口
  2. 元注解是什么:注解的注解,作用是為了標識目標注解。包括@Target,@Retention,@Documented,@Repeatable,@Inherited.
  3. 預置注解是什么:JDK自帶的經典功能注解,如@Override,@Deprecated,@SuppressWarnings,@SafeVarargs,@FunctionalInterface。
  4. 自定義注解如何實現:主要分為五步,但是其中必要的步驟,就一步:使用@interface來構建自定義注解。

至此,Java注解的內容就基本展現了。

最后,還是強調兩個方面:

  1. 注解就是增強型的注釋(可被計算機識別的注釋),本質是接口。把握住這兩點,就非常好理解注解與它的各種規則,行為。
  2. 注解本身並沒有任何功能(因為它只是注釋,本質也只是接口),需要其他代碼支撐,它才能體現價值。

希望對大家有所幫助,還有不清楚的地方,可以查看下列參考目錄。

願與諸君共進步。

附錄

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM