1. 程式人生 > >註解就這麽簡單

註解就這麽簡單

ava 十分 4.4 修飾 處理 生效 find ngs 做的

前言

今天要講的是註解,對於本章節,最好是有Servlet基礎的人查閱~因為單純是Java基礎的話,可能用不上註解這個東西。但如果開發過Servlet,就對@WebServlet不會陌生。

現在的開發都推崇使用註解來進行開發,這樣就可以免去寫XML配置了,十分方便的一項技術~

學習註解可以更好地理解註解是怎麽工作的,看見註解了就可以想到它的運行原理了~。

如果有錯的地方請大家多多包涵並歡迎在評論區指正~

一、什麽是註解?

註解:Annotation....

註解其實就是代碼中的特殊標記,這些標記可以在編譯、類加載、運行時被讀取,並執行相對應的處理

二、為什麽我們需要用到註解?

傳統的方式,我們是通過配置文件(xml文件)來告訴類是如何運行的

有了註解技術以後,我們就可以通過註解告訴類如何運行

例如:我們以前編寫Servlet的時候,需要在web.xml文件配置具體的信息

技術分享圖片

我們使用了註解以後,可以直接在Servlet源代碼上,增加註解...Servlet就被配置到Tomcat上了。也就是說,註解可以給類、方法上註入信息。

技術分享圖片

明顯地可以看出,這樣是非常直觀的,並且Servlet規範是推崇這種配置方式的

三、基本Annotation

在java.lang包下存在著5個基本的Annotation,其中有3個Annotation我們是非常常見的了。

3.1@Overried

重寫註解

如果我們使用IDE重寫父類的方法,我們就可以看見它了。那它有什麽用呢??

@Overried是告訴編譯器要檢查該方法是實現父類的...可以幫我們避免一些低級的錯誤...

比如,我們在實現equals()方法的時候,把euqals()打錯了,那麽編譯器就會發現該方法並不是實現父類的,與註解@Overried沖突,於是就會給予錯誤

技術分享圖片


3.2@Deprecated

過時註解

該註解也非常常見,Java在設計的時候,可能覺得某些方法設計得不好,為了兼容以前的程序,是不能直接把它拋棄的,於是就設置它為過時

Date對象中的toLocalString()就被設置成過時了

    @Deprecated
    public String toLocaleString() {
        DateFormat formatter = DateFormat.getDateTimeInstance
(); return formatter.format(this); }

當我們在程序中調用它的時候,在IDE上會出現一條橫杠,說明該方法是過時的。

技術分享圖片


3.3@SuppressWarnings

抑制編譯器警告註解

該註解在我們寫程序的時候並不是很常見,我們可以用它來讓編譯器不給予我們警告

當我們在使用集合的時候,如果沒有指定泛型,那麽會提示安全檢查的警告

技術分享圖片

如果我們在類上添加了@SuppressWarnings這個註解,那麽編譯器就不會給予我們警告了

技術分享圖片

3.4@SafeVarargs

Java 7“堆汙染”警告

什麽是堆汙染呢??當把一個不是泛型的集合賦值給一個帶泛型的集合的時候,這種情況就很容易發生堆汙染....

這個註解也是用來抑制編譯器警告的註解...用的地方並不多,我也不詳細說明了......有用到的時候再回來填坑吧。

3.5@FunctionalInterface

@FunctionalInterface用來指定該接口是函數式接口

用該註解顯示指定該接口是一個函數式接口。


四、自定義註解基礎

上面講解的是java.lang包下的5個註解,我們是可以自己來寫註解,給方法或類註入信息

4.1標記Annotation

沒有任何成員變量的註解稱作為標記註解,@Overried就是一個標記註解


//有點像定義一個接口一樣,只不過它多了一個@
public @interface MyAnnotation {
    
}

4.2元數據Annotation

我們自定義的註解是可以帶成員變量的,定義帶成員變量的註解叫做元數據Annotation

在註解中定義成員變量,語法類似於聲明方法一樣....


public @interface MyAnnotation {

    //定義了兩個成員變量
    String username();
    int age();
}

註意:在註解上定義的成員變量只能是String、數組、Class、枚舉類、註解

有的人可能會奇怪,為什麽註解上還要定義註解成員變量??聽起來就很復雜了....

上邊已經說了,註解的作用就是給類、方法註入信息。那麽我們經常使用XML文件,告訴程序怎麽運行。XML經常會有嵌套的情況


<書>
    <作者>zhongfucheng</作者>
    <價錢>22222</價錢>

</書>

那麽,當我們在使用註解的時候,也可能需要有嵌套的時候,所以就允許了註解上可以定義成員變量為註解。

4.3使用自定義註解

上面我們已經定義了一個註解了,下面我們來使用它吧

4.3.1常規使用

下面我有一個add的方法,需要username和age參數,我們通過註解來讓該方法擁有這兩個變量


    //註解擁有什麽屬性,在修飾的時候就要給出相對應的值
    @MyAnnotation(username = "zhongfucheng", age = 20)
    public void add(String username, int age) {

    }

4.3.2默認值

當然啦,我們可以在註解聲明屬性的時候,給出默認值。那麽在修飾的時候,就可以不用具體指定了。


public @interface MyAnnotation {

    //定義了兩個成員變量
    String username() default "zicheng";
    int age() default 23;
}
  • 在修飾的時候就不需要給出具體的值了

    @MyAnnotation()
    public void add(String username, int age) {

    }

4.3.3註解屬性為value

還有一種特殊的情況,如果註解上只有一個屬性,並且屬性的名稱為value,那麽在使用的時候,我們可以不寫value,直接賦值給它就行


public @interface MyAnnotation2 {

    String value();
}
  • 使用註解,可以不指定value,直接賦值

    @MyAnnotation2("zhongfucheng")
    public void find(String id) {
        
    }

4.4把自定義註解的基本信息註入到方法上

上面我們已經使用到了註解,但是目前為止註解上的信息和方法上的信息是沒有任何關聯的

我們使用Servlet註解的時候,僅僅調用註解,那麽註解的就生效了。這是Web容器把內部實現了。我們自己寫的自定義註解是需要我們自己來處理的

那現在問題來了,我們怎麽把註解上的信息註入到方法上呢???我們利用的是反射技術

步驟可分為三部:

  • 反射出該類的方法
  • 通過方法得到註解上具體的信息
  • 將註解上的信息註入到方法上

        //反射出該類的方法
        Class aClass = Demo2.class;
        Method method = aClass.getMethod("add", String.class, int.class);

        //通過該方法得到註解上的具體信息
        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
        String username = annotation.username();
        int age = annotation.age();

        //將註解上的信息註入到方法上
        Object o = aClass.newInstance();
        method.invoke(o, username, age);

當我們執行的時候,我們發現會出現異常...

技術分享圖片

此時,我們需要在自定義註解上加入這樣一句代碼(下面就會講到,為什麽要加入這句代碼)


    @Retention(RetentionPolicy.RUNTIME)

再次執行的時候,我們就會發現,可以通過註解來把信息註入到方法中了。


五、JDK的元Annotation

前面我們已經介紹了java.lang包下的幾個基本Annotation了。在JDK中除了java.lang包下有Annotation,在java.lang.annotation下也有幾個常用的元Annotation。

在annotation包下的好幾個元Annotation都是用於修飾其他的Annotation定義


5.1@Retention

上面在將註解信息註入到方法中的時候,我們最後加上了@Retention的註解....不然就會報錯了..那它是幹什麽用的呢?

@Retention只能用於修飾其他的Annotation,用於指定被修飾的Annotation被保留多長時間。

@Retention包含了一個RetentionPolicy類型的value變量,所以在使用它的時候,必須要為value成員變量賦值

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
}

java文件有三個時期:編譯,class,運行。@Retention默認是class

前面我們是使用反射來得到註解上的信息的,**因為@Retention默認是class,而反射是在運行時期來獲取信息的**。因此就獲取不到Annotation的信息了。於是,就得在自定義註解上修改它的RetentionPolicy值


5.2@Target

@Target也是只能用於修飾另外的Annotation它用於指定被修飾的Annotation用於修飾哪些程序單元

@Target是只有一個value成員變量的,該成員變量的值是以下的:


public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE
}

如果@Target指定的是ElementType.ANNOTATION_TYPE,那麽該被修飾的Annotation只能修飾Annotaion


5.3@Documented

@Documented用於指定被該Annotation修飾的Annotation類將被javadoc工具提取成文檔

該元Annotation用得挺少的....


5.4@Inherited

@Inherited也是用來修飾其他的Annotation的,被修飾過的Annotation將具有繼承性。。。

例子:

  1. @xxx是我自定義的註解,我現在使用@xxx註解在Base類上使用....
  2. 使用@Inherited修飾@xxx註解
  3. 當有類繼承了Base類的時候,該實現類自動擁有@xxx註解

六、註入對象到方法或成員變量上

6.1把對象註入到方法上

前面我們已經可以使用註解將基本的信息註入到方法上了,現在我們要使用的是將對象註入到方法上.....

上邊已經說過了,註解上只能定義String、枚舉類、Double之類的成員變量,那怎麽把對象註入到方法上呢?

6.1.2模擬場景:

  • Person類,定義username和age屬性,擁有uername和age的getter和setter方法

public class Person {
    
    private String username;
    private int age;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • PersonDao類,PersonDao類定義了Person對象,擁有person的setter和getter方法

public class PersonDao {
    
    private Person person;

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
}
  • 現在我要做的就是:使用註解將Person對象註入到setPerson()方法中,從而設置了PersonDao類的person屬性

public class PersonDao {

    private Person person;

    public Person getPerson() {
        return person;
    }


    //將username為zhongfucheng,age為20的Person對象註入到setPerson方法中
    @InjectPerson(username = "zhongfucheng",age = 20)
    public void setPerson(Person person) {
        
        this.person = person;
    }
}

步驟:

①: 自定義一個註解,屬性是和JavaBean類一致的


//註入工具是通過反射來得到註解的信息的,於是保留域必須使用RunTime
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectPerson {

    String username();
    int age();
}

②:編寫註入工具


        //1.使用內省【後邊需要得到屬性的寫方法】,得到想要註入的屬性
        PropertyDescriptor descriptor = new PropertyDescriptor("person", PersonDao.class);

        //2.得到要想註入屬性的具體對象
        Person person = (Person) descriptor.getPropertyType().newInstance();

        //3.得到該屬性的寫方法【setPerson()】
        Method method = descriptor.getWriteMethod();

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

        //5.得到註解上的信息【註解的成員變量就是用方法來定義的】
        Method[] methods = annotation.getClass().getMethods();

        //6.將註解上的信息填充到person對象上

        for (Method m : methods) {

            //得到註解上屬性的名字【age或name】
            String name = m.getName();

            //看看Person對象有沒有與之對應的方法【setAge(),setName()】
            try {

                //6.1這裏假設:有與之對應的寫方法,得到寫方法
                PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Person.class);
                Method method1 = descriptor1.getWriteMethod();//setAge(), setName()

                //得到註解中的值
                Object o = m.invoke(annotation, null);

                //調用Person對象的setter方法,將註解上的值設置進去
                method1.invoke(person, o);

            } catch (Exception e) {

                //6.2 Person對象沒有與之對應的方法,會跳到catch來。我們要讓它繼續遍歷註解就好了
                continue;
            }
        }
        
        //當程序遍歷完之後,person對象已經填充完數據了

        //7.將person對象賦給PersonDao【通過寫方法】
        PersonDao personDao = new PersonDao();
        method.invoke(personDao, person);

        System.out.println(personDao.getPerson().getUsername());
        System.out.println(personDao.getPerson().getAge());

③:總結一下步驟

其實我們是這樣把對象註入到方法中的:

  • 得到想要類中註入的屬性
  • 得到該屬性的對象
  • 得到屬性對應的寫方法
  • 通過寫方法得到註解
  • 獲取註解詳細的信息
  • 將註解的信息註入到對象上
  • 調用屬性寫方法,將已填充數據的對象註入到方法中

6.2把對象註入到成員變量

上面已經說了如何將對象註入到方法上了,那麽註入到成員變量上也是非常簡單的。

步驟:

①:在成員變量上使用註解


public class PersonDao {

    @InjectPerson(username = "zhongfucheng",age = 20) private Person person;

    public Person getPerson() {
        return person;
    }
    
    public void setPerson(Person person) {

        this.person = person;
    }
}

②:編寫註入工具


        //1.得到想要註入的屬性
        Field field = PersonDao.class.getDeclaredField("person");

        //2.得到屬性的具體對象
        Person person = (Person) field.getType().newInstance();

        //3.得到屬性上的註解
        Annotation annotation = field.getAnnotation(InjectPerson.class);

        //4.得到註解的屬性【註解上的屬性使用方法來表示的】
        Method[] methods = annotation.getClass().getMethods();

        //5.將註入的屬性填充到person對象上
        for (Method method : methods) {

            //5.1得到註解屬性的名字
            String name = method.getName();

            //查看一下Person對象上有沒有與之對應的寫方法
            try {

                //如果有
                PropertyDescriptor descriptor = new PropertyDescriptor(name, Person.class);

                //得到Person對象上的寫方法
                Method method1 = descriptor.getWriteMethod();

                //得到註解上的值
                Object o = method.invoke(annotation, null);

                //填充person對象
                method1.invoke(person, o);
            } catch (IntrospectionException e) {

                //如果沒有想對應的屬性,繼續循環
                continue;
            }
        }

        //循環完之後,person就已經填充好數據了


        //6.把person對象設置到PersonDao中
        PersonDao personDao = new PersonDao();
        field.setAccessible(true);
        field.set(personDao, person);

        System.out.println(personDao.getPerson().getUsername());

七、總結

①:註入對象的步驟:得到想要註入的對象屬性,通過屬性得到註解的信息,通過屬性的寫方法將註解的信息註入到對象上,最後將對象賦給類

②:註解其實就是兩個作用:

  • 讓編譯器檢查代碼
  • 將數據註入到方法、成員變量、類上

③:在JDK中註解分為了

  • 基本Annotation
    • 在lang包下,用於常用於標記該方法,抑制編譯器警告等
  • 元Annotaion
    • 在annotaion包下,常用於修飾其他的Annotation定義

技術分享圖片

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關註微信公眾號:Java3y

註解就這麽簡單