天天看點

Java 注解

一、注解 (Annotation) 概述

從 JDK 5.0 開始, Java 增加了對中繼資料(MetaData) 的支援, 也就是Annotation(注解)

Annotation 其實就是代碼裡的特殊标記, 這些标記可以在編譯, 類加載, 運作時被讀取, 并執行相應的處理。通過使用 Annotation, 程式員可以在不改變原有邏輯的情況下, 在源檔案中嵌入一些補充資訊。代碼分析工具、開發工具和部署工具可以通過這些補充資訊進行驗證或者進行部署。

Annotation 可以像修飾符一樣被使用, 可用于修飾包,類, 構造器, 方法, 成員變量, 參數, 局部變量的聲明, 這些資訊被儲存在 Annotation 的 “name=value” 對中。

在JavaSE中,注解的使用目的比較簡單,例如标記過時的功能,忽略警告等。在JavaEE/Android中注解占據了更重要的角色,例如用來配置應用程式的任何切面,代替JavaEE舊版中所遺留的繁冗代碼和XML配置等。

未來的開發模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以後也是基于注解的,現在的Struts2有一部分也是基于注解的了,注解是一種趨勢,一定程度上可以說:架構 = 注解 + 反射 + 設計模式。

注解都是 @ 符号開頭的,例如我們在學習方法重寫時使用過的 @Override 注解。同 Class 和 Interface 一樣,注解也屬于一種類型。

無論是哪一種注解,本質上都一種資料類型,是一種接口類型。到 Java 8 為止 Java SE 提供了 11 個内置注解。其中有 5 個是基本注解,它們來自于 java.lang 包。有 6 個是元注解,它們來自于 java.lang.annotation 包,自定義注解會用到元注解。

提示:元注解就是負責注解其他的注解。

基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs 和 @FunctionalInterface。

二、常見的Annotation示例

使用 Annotation 時要在其前面增加 @ 符号, 并把該 Annotation 當成

一個修飾符使用。用于修飾它支援的程式元素

1、生成文檔相關的注解

  • @author 标明開發該類子產品的作者,多個作者之間使用,分割
  • @version 标明該類子產品的版本
  • @see 參考轉向,也就是相關主題
  • @since 從哪個版本開始增加的
  • @param 對方法中某參數的說明,如果沒有參數就不能寫
  • @return 對方法傳回值的說明,如果方法的傳回值類型是void就不能寫
  • @exception 對方法可能抛出的異常進行說明 ,如果方法沒有用throws顯式抛出的異常就不能寫

其中

@param @return 和 @exception 這三個标記都是隻用于方法的。

@param的格式要求:@param 形參名 形參類型 形參說明

@return 的格式要求:@return 傳回值類型 傳回值說明

@exception的格式要求:@exception 異常類型 異常說明

@param和@exception可以并列多個

package com.annotation.javadoc;
/**
 * @author shkstart
 * @version 1.0
 * @see Math.java
 */
public class JavadocTest {
    /**
     * 程式的主方法,程式的入口
     * @param args String[] 指令行參數
     */
    public static void main(String[] args) {
    }
    /**
     * 求圓面積的方法
     * @param radius double 半徑值
     * @return double 圓的面積
     */
    public static double getArea(double radius){
        return Math.PI * radius * radius; } 
}      

2、JDK内置的基本注解

  • @Override 注解是用來指定方法重寫的,隻能修飾方法并且隻能用于方法重寫,不能修飾其它的元素。它可以強制一個子類必須重寫父類方法或者實作接口的方法。
  • @Deprecated 可以用來注解類、接口、成員方法和成員變量等,用于表示某個元素(類、方法等)已過時。通常是因為所修飾的結構危險或存在更好的選擇。當其他程式使用已過時的元素時,編譯器将會給出警告。
  • @SuppressWarnings 注解訓示被該注解修飾的程式元素(以及該程式元素中的所有子元素)取消顯示指定的編譯器警告,且會一直作用于該程式元素的所有子元素。例如,使@SuppressWarnings 修飾某個類取消顯示某個編譯器警告,同時又修飾該類裡的某個方法取消顯示另一個編譯器警告,那麼該方法将會同時取消顯示這兩個編譯器警告。
package com.annotation.javadoc;
public class AnnotationTest{
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        int a = 10;
    }
    @Deprecated
    public void print(){
        System.out.println("過時的方法");
    }
    @Override
    public String toString() {
        return "重寫的toString方法()"; 
    } 
}      

3、跟蹤代碼依賴性,實作替代配置檔案功能

Servlet3.0提供了注解(annotation),使得不再需要在web.xml檔案中進行Servlet的部署。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
            ServletException, IOException { }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
            ServletException, IOException {
        doGet(request, response);
    } }      

web.xml

<servlet>      
<servlet-name>LoginServlet</servlet-name>      
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>      
<servlet-mapping>      
<servlet-name>LoginServlet</servlet-name>      
<url-pattern>/login</url-pattern>
</servlet-mapping>      

spring架構中關于“事務”的管理

@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username, String isbn) {
//1.查詢書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新庫存
        bookShopDao.updateBookStock(isbn);
//3. 更新使用者的餘額
        bookShopDao.updateUserAccount(username, price); 
}      
<!-- 配置事務屬性 --> <tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<!-- 配置每個方法使用的事務屬性 --> <tx:method name="buyBook" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" read-only="false" timeout="3" />
</tx:attributes>
</tx:advice>      

三、自定義Annotation

  • 定義新的 Annotation 類型使用 @interface 關鍵字
  • 自定義注解自動繼承了java.lang.annotation.Annotation接口
  • Annotation 的成員變量在 Annotation 定義中以無參數方法的形式來聲明。其方法名和傳回值定義了該成員的名字和類型。我們稱為配置參數。類型隻能是八種基本資料類型、String類型、Class類型、enum類型、Annotation類型、以上所有類型的數組。  可以在定義 Annotation 的成員變量時為其指定初始值, 指定成員變量的初始值可使用 default 關鍵字
  • 如果隻有一個參數成員,建議使用參數名為value
  • 如果定義的注解含有配置參數,那麼使用時必須指定參數值,除非它有預設值。格式是“參數名 = 參數值”,如果隻有一個參數成員,且名稱為value 可以省略“value=”
  • 沒有成員定義的 Annotation 稱為标記; 包含成員變量的 Annotation 稱為中繼資料 Annotation

注意:自定義注解必須配上注解的資訊處理流程才有意義。

聲明自定義注解使用 @interface 關鍵字(interface 關鍵字前加 @ 符号)實作。定義注解與定義接口非常像,如下代碼可定義一個簡單形式的注解類型。

public @interface MyAnnotation {

    String value() default "hello";
}

public @interface MyAnnotations {

    MyAnnotation[] value();
}      

四、JDK 中的元注解

JDK 的元 Annotation 用于修飾其他 Annotation 定義

JDK5.0提供了4個标準的meta-annotation類型,分别是:

  • Retention
  • Target
  • Documented
  • Inherited

Java 8 又增加了 @Repeatable 和 @Native 兩個注解。這些注解都可以在 java.lang.annotation 包中找到。下面主要介紹每個元注解的作用及使用。

1、@Retention

@Retention: 隻能用于修飾一個 Annotation 定義, 用于指定該 Annotation 的生命周期, @Rentention 包含一個 RetentionPolicy 類型的成員變量, 使用@Rentention 時必須為該 value 成員變量指定值:

  • RetentionPolicy.SOURCE:在源檔案中有效(即源檔案保留),編譯器直接丢棄這種政策的注釋
  • RetentionPolicy.CLASS:在class檔案中有效(即class保留) , 當運作 Java 程式時, JVM 不會保留注解。 這是預設值
  • RetentionPolicy.RUNTIME:在運作時有效(即運作時保留),當運作 Java 程式時, JVM 會保留注釋。程式可以通過反射擷取該注釋。
Java 注解

2、@Target

@Target: 用于修飾 Annotation 定義, 用于指定被修飾的 Annotation 能用于修飾哪些程式元素。 @Target 也包含一個名為 value 的成員變量。

Java 注解

JDK1.8之後,關于元注解@Target的參數類型ElementType枚舉值多了兩個:

TYPE_PARAMETER,TYPE_USE。

在Java 8之前,注解隻能是在聲明的地方所使用,Java8開始,注解可以應用在任何地方。 

  • ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中(如:泛型聲明)。 
  • ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中。

3、@Documented

@Documented: 用于指定被該元 Annotation 修飾的 Annotation 類将被javadoc 工具提取成文檔。預設情況下,javadoc是不包括注解的。

定義為Documented的注解必須設定Retention值為RUNTIME。

4、@Inherited

@Inherited: 被它修飾的 Annotation 将具有繼承性。如果某個類使用了被@Inherited 修飾的 Annotation, 則其子類将自動具有該注解。

  • 比如:如果把标有@Inherited注解的自定義的注解标注在類級别上,子類則可以繼承父類類級别的注解
  • 實際應用中,使用較少
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {

    String value() default "hello";
}      

5、@Repeatable

@Repeatable 注解是 Java 8 新增加的,它允許在相同的程式元素中重複注解,在需要對同一種注解多次使用時,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一個程式元素前最多隻能有一個相同類型的注解,如果需要在同一個元素前使用多個相同類型的注解,則必須使用注解“容器”。

Java 8之前

public @interface Roles {
    Role[] value();
}

public @interface Role {
    String roleName();
}

public class RoleTest { 
    @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")}) 
    public String doString(){ 
        return "test";
    } 
}      

Java 8 之後增加了重複注解,使用方式如下:

//先聲明一個Roles用來包含所有的身份      
public @interface Roles {
        Role[] value();
    }
    @Repeatable(Roles.class)
    public @interface Role {
        String roleName();
    }
    public class RoleTest {
        @Role(roleName = "role1")
        @Role(roleName = "role2")
        public String doString(){
            return "test";
        }
    }      

6、@Native

使用 @Native 注解修飾成員變量,則表示這個變量可以被本地代碼引用,常常被代碼生成工具使用。對于 @Native 注解不常使用,了解即可。

六、利用反射擷取注解資訊

JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 該接口代表程式中可以接受注解的程式元素

當一個 Annotation 類型被定義為運作時 Annotation 後, 該注解才是運作時可見, 當 class 檔案被載入時儲存在 class 檔案中的 Annotation 才會被虛拟機讀取

程式可以調用 AnnotatedElement對象的如下方法來通路 Annotation 資訊

Java 注解
@Retention(RetentionPolicy.RUNTIME)
@interface PersonInfo {
    String name();
    int age() default 20;
    String gender();
}

class PersonOpe {
    @PersonInfo(name="李四",age=20,gender="男")
    public void show(String name,int age,String gen) {
        System.out.println(name);
        System.out.println(age);
        System.out.println(gen);
    }
}
public class AnnotationTest {
    public static void main(String[] args) throws Exception{
        PersonOpe ope=new PersonOpe();
        Class<?> class1=PersonOpe.class;
        Method method = class1.getMethod("show", String.class,int.class,String.class);
        PersonInfo annotation = method.getAnnotation(PersonInfo.class);
        String name=annotation.name();
        int age=annotation.age();
        String gender=annotation.gender();
        method.invoke(ope, name,age,gender);
    }
}      

作者:王陸