註解,就是那麼簡單!!!

深刻理解註解

一、基本介紹

基本概念註解,顧名思義,就是對某一事物進行添加註釋說明,會存放一些信息,這些信息可能對之後某個時段來講是頗有用處的。Java 註解(Annotation)又稱 Java 標註,是 JDK5.0 引入的一種註釋機制。Java 語言中的方法變量參數等均可以被標註(添加某些信息)。在編譯器生成類文件時,標註能夠被嵌入到字節碼中。Java 虛擬機能夠保留標註內容,在運行時能夠經過反射的方式獲取到標註內容 。 固然它也支持自定義 的Java 標註。java

註解與註釋的區別程序員

定義不一樣:註解與類、接口在同一層次的,是一種描述數據的數據,能夠理解爲註解就是源代碼的元數據。註釋則是對源代碼的介紹,方便開發者理解代碼的所撰寫的文字。數組

做用不一樣:註解是Java 編譯器能夠理解的部分,是給編譯器看的。經過標記包、類、字段、方法、局部變量、方法參數等元數據,告訴jvm這些元數據的信息。註釋是程序員對源代碼作一些記憶或提示性描述,是給人來看的。它能告訴開發者這段代碼的邏輯、說明、特色等內容,對代碼起到解釋、說明的做用。markdown

使用範圍不一樣:使用範圍不一樣:註解 ,參與代碼編譯,以@開頭的,與工具一塊兒使用。對於位置、語法、內容有必定的限制。註釋 ,能夠隨意在任務位置填寫內容,對代碼任何沒有影響。jvm

總之,註解能夠理解爲對類、變量、方法和接口進行規範和約束,註釋則理爲開發者對代碼進行解釋而撰寫的文字。ide

註解能夠根據來源能夠分爲系統註解自定義註解第三方註解,系統註解根據用途能夠分爲內置註解元註解,在下面的文章中,咱們主要講解內置註解元註解自定義註解工具

2 內置註解

在java.lang包下存在着咱們常常看到的註解,分別是@Deprecated@Override和@SuppressWarnings測試

2.1@Deprecated註解

@Deprecated能夠修飾方法變量,被@Deprecated修飾後表示不建議使用,它的存在僅僅是爲了兼容之前的程序,因爲不能直接把它拋棄,因此將它設置爲過期。可是被這個註解修飾的類、方法在高版本的JDK中使用時了可能會出現錯誤。this

源代碼以下編碼

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
複製代碼

其中@Documented、@Retention和@Target是元註解,咱們在下文介紹

2.2@Override註解

它代表了被註解的方法須要重寫父類中的方法,若是某個方法使用了該註解,卻沒有覆寫超類中的方法,編譯器就會報出錯誤。在子類中重寫父類或接口的方法,@Overide並非必須的。可是仍是建議使用這個註解,由於在某些狀況下,假設你修改了父類的方法的名字,那麼以前重寫的子類方法將再也不屬於重寫,若是沒有@Overide,你將不會察覺到這個子類的方法。有了這個註解修飾,編譯器則會提示你這些信息。

源代碼以下

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
複製代碼

2.3@SuppressWarnings註解

@SuppressWarnings用來抑制編譯器生成警告信息,能夠修飾的元素爲方法方法參數屬性局部變量。它能夠達到抑制編譯器編譯時產生警告的目的,使用@SuppressWarnings註解,採用就近原則,好比一個方法出現警告,儘可能使用@SuppressWarnings註解這個方法,而不是註解方法所在的類。所屬範圍越小越好,由於範圍大了,不利於發現該類下其餘方法的警告信息。 可是咱們一般不建議使用@SuppressWarnings註解,使用此註解,開發人員看不到編譯時編譯器提示的相應的警告,不能選擇更好、更新的類、方法或者不能編寫更規範的編碼。同時後期更新JDK、jar包等源碼時,使用@SuppressWarnings註解的代碼可能受新的JDK、jar包代碼的支持,出現錯誤,仍然須要修改。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
   String[] value();
}
複製代碼

三、元註解簡介

在上面的代碼中,咱們看到了註解上還有註解,這種修飾註解的註解被稱爲元註解。事實上,元註解(meta-annotation)的做用就是註解其它的註解,Java在java.lang.annotation包中定義了4個標準的元註解類型,分別爲@Target@Retention@Documented@Inherited

3.1@Target

@Target用於描述註解的使用範圍,即被描述的註解能夠用到什麼地方,@Target註解內定義了ElemenType[]數組,數組以枚舉類的形式定義了註解的修飾範圍。經過下面的源代碼咱們能夠看出,該註解可以用於接口構造器屬性方法參數聲明註解聲明

源代碼以下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
 
    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 * * @since 1.8 */
    TYPE_PARAMETER,

    /** * Use of a type * * @since 1.8 */
    TYPE_USE
}
複製代碼

3.2@Rentention

@Rentention表示須要在什麼級別保存該註釋信息,用於描述註解的生命週期,經過源代碼,咱們能夠看出,註解內有個RetentionPolicy的值,咱們繼續深刻往下看,RetentionPolicy是個枚舉類型的值,它有三個值可供選擇,SOURCE是在源代碼層面,在編譯是將會失效;CLASS做用在class文件中,可是在運行時失效RUNTIME在運行時依舊保留該註解,所以能夠經過反射機制來讀取註解內的信息。所以,這三個值對應的生命週期大小爲:SOURCE<CLASS<RUNTIME,若是不手動添加的話,則默認爲CLASS。

源代碼以下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /** * Returns the retention policy. * @return the retention policy */
    RetentionPolicy value();
}
複製代碼
public enum RetentionPolicy {
    /** * Annotations are to be discarded by the compiler. */
    SOURCE,

    /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */
    CLASS,

    /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */
    RUNTIME
}
複製代碼

3.3@Documented

@Documented代表該註解將被包含在javadoc中。該註解用的相對較少。

源代碼以下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
複製代碼

3.4@Inherited

@Inherited說明子類能夠繼承父類中的該註解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
複製代碼

四、自定義註解

經過使用元註解的組合,咱們能夠按照本身的需求來自定義註解,咱們一般使用@interface來自定義註解,它自動繼承了java.lang.annotation.Annotation接口。接下,咱們來以一段代碼仔細分析下如何自定義註解:

  • 註解一
public @interface MyAnnotation{
}
複製代碼

註解一是一個最簡單的自定義註解,咱們能夠看到,它以public修飾,以@interface用來聲明一個註解,具體的格式爲:public @interface 註解名{定義內容},若是要在註解內添加一個參數,該怎樣定義呢?

  • 註解二
@Retention(value = RetentionPolicy.RUNTIME)
@interface myAnnotantion3{
    //參數名爲vale,當註解內只有一個參數,使用註解時,參數名可省略
    String value();
}
複製代碼

特別須要注意的是,註解內的方法名稱就是參數的名稱,而返回值類型就是參數類型,咱們能夠這樣使用

@Retention(value = RetentionPolicy.RUNTIME)
@myAnnotantion3("snow")
    public void test2(){
    }
複製代碼

由於註解內只有一個參數,因此在使用註解時,參數名稱是能夠省略的。

若是,咱們咱們想添加多個的參數值,該怎麼自定義註解呢

  • 註解三
Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface myAnnotaion2{
    /** *表示註解的參數,name參數名,String 表示參數的類型 * 參數加default,在註解內可寫可不寫 */
    String name();
    int age();
    int id();
}
複製代碼

同理,咱們只須要在相應的位置引用該註解便可

@myAnnotaion2(name = "Simon",age=25,id=23)
    public void test1(){
    }
複製代碼

若是咱們向給註解內的參數設定默認值,咱們能夠這樣作

  • 註解四
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface myAnnotaion2{
    /** *表示註解的參數,name參數名,String 表示參數的類型 * 參數加default,在註解內可寫可不寫 */
    String name() default "";
    int age() default 0;
    int id() default -1;
}
複製代碼

在實際的方法使用中,咱們只須要給必需要修改的值賦值便可

@myAnnotaion2(name = "Simon")
    public void test1(){
        System.out.println("測試註解1");
    }
複製代碼

所以,自定義註解能夠概括以下:

  • @interface用來聲明一個註解,格式:public @interface 註解名{定義內容}

  • 其中每個方法實際上聲明瞭一個配置參數

  • 方法的名稱就是參數的名稱

  • 返回值類型就是參數的類型(返回值只能時基本類型,Class,String,enum)

  • 能夠經過default用來聲明參數的默認值

  • 若是隻有一個參數成員,通常參數名爲value

  • 註解元素必需要有值,咱們定義註解元素時,常用空字符串0做爲默認值

五、獲取註解中的參數值

在以上的講解中,咱們使用註解都是對所修飾的類、方法、變量進行規範和約束,在大多數使用場景中,以方法爲例,咱們須要將註解中的信息同方法聯繫起來,即將註解中的參數信息的注入到方法中。

5.1參數值是基本類型

  • 首次,咱們建立一個帶有參數的註解
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation5 {
    String name() default " ";

    int age() default 0;
}
複製代碼
  • 將該註解修飾到某一方法上
@MyAnnotation5(name = "Simon", age = 25)
    public void testInjectValue(String name,int age) {

        System.out.println("獲取註解中的參數值:");
        System.out.println(name);
        System.out.println(age);
    }  
複製代碼
  • 反射獲取註解中的參數並注入到方法中
  • 反射獲取該類的方法
  • 經過方法獲取註解中的參數值
  • 將註解中的參數值注入到相應的方法中
//反射獲取類,並獲得類中的方法 
 	    Class aClass = InjectValue.class;
        Method method=aClass.getMethod("testInjectValue",String.class,int.class);
		//獲取註解中的屬性值
        MyAnnotation5 myAnnotation5=method.getAnnotation(MyAnnotation5.class);
        String name=myAnnotation5.name();
        int age=myAnnotation5.age();
		//將屬性值注入到相應的方法中
        Object o=aClass.newInstance();
        method.invoke(o,name,age);
複製代碼

5.2參數值是對象

前面咱們講解如何將註解中的參數爲基本數據類型注入到方法中,那麼如何將註解中的參數爲對象注入到方法中呢?

  • 建立一個類用於生成對象
public class Animal {
   private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
複製代碼
  • 建立一個類,類中的屬性是上一個類的對象,並建立一個帶參數的註解,參數的類型爲對象
public class AnimalDao {
  private Animal animal;

    public Animal getAnimal() {
        return animal;
    }

    @MyAnnotation6(name = "Dog",age = 12)
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}
複製代碼
  • 獲取註解中的對象
public class TestInjectObject {
    public static void main(String[] args) throws Exception {
        //1.使用PropertyDescriptor獲得想要注入的屬性
        PropertyDescriptor descriptor = new PropertyDescriptor("animal", AnimalDao.class);

        //2.獲得要想注入屬性的具體對象
        Animal animal = (Animal) descriptor.getPropertyType().newInstance();

        //3.獲得該屬性的寫方法
        Method method = descriptor.getWriteMethod();

        //4.獲得寫方法的註解
        Annotation annotation = method.getAnnotation(MyAnnotation6.class);

        //5.獲得註解上的信息
        Method[] methods = annotation.getClass().getMethods();

        //6.將註解上的信息填充到animal對象上
        for (Method m : methods) {
            //獲得註解上屬性的名字
            String name = m.getName();
            //看看animal對象有沒有與之對應的方法
            try {

                PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Animal.class);
                Method method1 = descriptor1.getWriteMethod();
                //獲得註解中的值
                Object o = m.invoke(annotation, null);
                //調用animal對象的setter方法,將註解上的值設置進去
                method1.invoke(animal, o);

            } catch (Exception e) {
                continue;
            }
        }
        AnimalDao animalDao = new AnimalDao();
        method.invoke(animalDao, animal);

        System.out.println(animalDao.getAnimal().getName());
        System.out.println(animalDao.getAnimal().getAge());
    }

}

@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation6 {
    String name();

    int age();
}
複製代碼

所以,將對象注入到方法中能夠總結以下

  • 建立想要得到屬性的對象
  • 根據對象獲取該屬性的方法
  • 獲得方法中的註解
  • 獲取註解中的信息
  • 將註解信息注入到對象中
  • 將對象中的屬性寫入到方法中
相關文章
相關標籤/搜索