[Java5新特性]Annotation註解

作者信息
作者姓名:金雲龍
個人網站:http://www.longestory.com
個人公衆帳號:搜索“longestory”或“龍哥有話說

Annotation概述

Annotation是JDK 5.0以後提供對元數據的支持,可以在編譯、加載和運行時被讀取,並執行相應的處理。所謂Annotation就是提供了一種爲程序元素設置元數據的方法,可用於修飾包、類、構造器、方法、成員變量、參數和局部變量的聲明,這些信息被存儲在Annotation的“name=value”對中。

Annotation能被用來爲程序元素(類、方法、成員變量等)設置元數據,比如一段代碼的作者或者告訴編譯器禁止一些特殊的錯誤,不會影響代碼的執行。

基本Annotation

在Java中提供了3個基本Annotation的用法,使用Annotation時要在其前面增加@符號,並把該Annotation當作一個修飾符使用,用於修飾它支持的程序元素。這3個基本Annotation都定義在java.lang包下,可以通過查看API文檔來了解。

  • @Override:限定重寫父類方法。@Override就是用來指定方法覆載的,它可以強制一個子類必須覆蓋父類的方法。
public class Fruit {
    public void info(){
        System.out.println("這是一個水果,想吃嗎?");
    }
}
public class Apple extends Fruit {
    @Override
    public void info() {
        System.out.println("這不僅是一個水果,它是蘋果.");
    }
}

如果Apple類的info()方法名寫成了inf()的話,編譯器會報錯。值得注意的是,@Override只能修飾方法,不能修飾其他程序元素。

  • @Deprecated:標示已過時。@Deprecated用於表示某個程序元素已過時,當其他程序使用已過時的類、方法時,編譯器將會給出警告。
public class Fruit {
    @Deprecated
    public void info(){
        System.out.println("這是一個水果,想吃嗎?");
    }
}
public class DeprecatedTest {
    public static void main(String[] args) {
        // 使用info()方法時將會出現劃線,表示該方法已過時.
        new Fruit().info();
    }
}
  • @SuppressWarnings:抑制編譯器警告。@SuppressWarnings表示被該Annotation修飾的代碼取消顯示指定的編譯器警告。
public class SuppressWarningsTest {
    public static void main(String[] args) {
        @SuppressWarnings("rawtypes")
        /*
         * List集合在定義時,沒有指定泛型類型.
         *  * 默認情況下,出現編譯器警告.
         *  * 使用@SuppressWarnings註釋後,取消警告信息.
         */
        List list = new ArrayList();
    }
}

自定義Annotation

自定義一個Annotation類型使用@interface關鍵字,定義一個新的Annotation類型與定義一個接口非常像(只是多了一個@符號)。

// 自定義一個Annotation類型
public @interface Test {
}

在自定義一個Annotation類型通常可以用於修飾程序中的類、方法、變量、接口等。一般情況下,使用Annotation會在代碼之前使用。

// 自定義Annotation類型定義在類上.
@Test
public class AnnotationTest {
    // 自定義Annotation類型定義在成員變量上.
    @Test
    private int i;
    // 自定義Annotation類型定義在構造函數上.
    @Test
    public AnnotationTest(){}
    // 自定義Annotation類型定義在方法上.
    @Test
    // 自定義Annotation類型定義在方法參數上.
    public void fun(@Test String str){
        // 自定義Annotation類型定義在變量上.
        @Test
        int z;
    }
}

Annotation屬性

自定義Annotation不僅可以是這種簡單形式,還可以包含成員變量。自定義的Annotation的成員變量以無形參的方法形式來聲明,其方法名和返回值定義了該成員變量的名字和類型。

/**
 * 自定義帶有username和password屬性的Annotation
 * 
 * @author 金雲龍
 */
public @interface UserInfo {
    String username();
    String password();
}

使用帶有屬性的自定義Annotation時,必須使用其屬性指定值,否則會報錯。

@UserInfo(username="zhangwuji",password="123")
public class UserInfoTest {
}

自定義Annotation不僅可以設置屬性,還可以爲屬性設置默認值,使用default關鍵字。

/**
 * 自定義帶有username和password屬性的Annotation
 *  * 爲username屬性設置默認值.
 * @author 金雲龍
 */
public @interface UserInfo {
    String username() default "zhangwuji";
    String password();
}

如果爲自定義Annotation的屬性設置了默認值,則在使用時可以不爲該屬性指定值(使用默認值)。也可以在使用該Annotation時爲其屬性指定值,則默認值不會起作用。

自定義Annotation中具有名爲value的屬性,在使用該Annotation時如果只使用value屬性的話,可以不寫屬性名直接指定值。

@UserInfo("jiaozhu")
public class UserInfoTest {
}

Annotation的屬性類型只能是基本類型、String、Enum、Class及上述類型的一維數組類型。

@Target註解

@Target修飾自定義Annotation,指定該自定義Annotation可以用於修飾哪些程序單元,例如方法、成員變量等。@Target註解包含一個ElementType類型的value屬性,該屬性值只能是如下幾個:

  • ElementType.ANNOTATION_TYPE:指定該策略的Annotation只能修飾Annotation。
  • ElementType.CONSTRUCTOR:指定該策略的Annotation只能修飾構造器。
  • ElementType.FIELD:指定該策略的Annotation只能修飾成員變量。
  • ElementType.LOCAL_VARIABLE:指定該策略的Annotation只能修飾局部變量。
  • ElementType.METHOD:指定該策略的Annotation只能修飾方法定義。
  • ElementType.PACKAGE:指定該策略的Annotation只能修飾包定義。
  • ElementType.PARAMETER:指定該策略的Annotation只能修飾參數。
  • ElementType.TYPE:指定該策略的Annotation可以修飾類、接口或枚舉定義。

以下是@Target註解的源碼和ElementType的源碼:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
public enum ElementType {
    TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE
}

@Retention註解

@Retention修飾自定義Annotation,指定自定義Annotation的生命週期。@Retention包含一個RetentionPolicy類型的value屬性,該屬性值只能是如下幾個:

  • RetentionPolicy.CLASS:編譯器將把Annotation記錄在class文件中。當運行Java程序時,JVM不可獲取Annotation信息。這時默認值。
  • RetentionPolicy.RUNTIME:編譯器將把Annotation記錄在class文件中。當運行Java程序時,JVM也可以獲取Annotation信息,程序可以通過反射獲取該Annotation信息。
  • RetentionPolicy.SOURCE:Annotation只保留在源代碼中,編譯器直接丟棄這種Annotation。

以下是@Retention註解的源碼和RetentionPolicy的源碼:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
public enum RetentionPolicy {
    SOURCE, CLASS, RUNTIME
}

反射讀取Annotation

使用Annotation修飾了類、方法、成員變量等之後,這些Annotation不會自己生效,必須通過相應程序提取並處理Annotation信息。Java提供的Annotation接口是所有註解的父接口,在JDK 5.0新增加AnnotatedElement接口,該接口提供讀取運行時Annotation的方法。只有當自定義的Annotation使用了@Retention(RetentionPolicy.RUNTIME)時,該Annotation纔會在運行可見,JVM才能讀取保存在class文件的Annotation信息。

以下是AnnotatedElement接口提供的方法API:

方法摘要
<T extends Annotation> T getAnnotation(Class annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

實際獲取某類使用的Annotation信息的方式如下:

public class AnnotatedElementTest {
    public static void main(String[] args) throws Exception {
        // 獲取對應類的Class對象.
        Class<UserInfoTest> clazz = UserInfoTest.class;
        // 獲取對應類方法的Method對象.
        Method method = clazz.getMethod("fun");
        // 獲取類上的註解.
        UserInfo anno1 = clazz.getAnnotation(UserInfo.class);
        // 打印該註解的username屬性值.
        System.out.println(anno1.username());
        // 獲取方法上的註解.
        UserInfo anno2 = method.getAnnotation(UserInfo.class);
        // 打印該註解的username屬性值.
        System.out.println(anno2.password());
    }
}

註解配置JDBC案例

使用JDBC連接MySQL數據庫時,需要driverClassName、url、username和password四個參數。而之前的做法是將這四個參數寫入一個配置文件,在JDBCUtils工具類中讀取配置文件。目前可以將四個參數定義爲一個註解,在JDBCUtils工具類中通過反射獲取對應註解定義的四個參數內容。具體做法如下:

  • 定義一個Annotation用於定義JDBC連接MySQL數據庫所需的四個參數內容。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JDBCInfo {
    String driverClassName();
    String url();
    String username();
    String password();
}
  • 定義JDBCUtils工具類,使用Annotation配置四個參數內容,並使用反射進行讀取。
public class JDBCUtils {
    @JDBCInfo(driverClassName = "com.mysql.jdbc.Driver", url = "jdbc:mysql://localhost:3306/jdbc", username = "root", password = "root")
    public static Connection getConnection() throws Exception {
        // 獲取註解修飾目標對應的反射對象.
        Method method = JDBCUtils.class.getDeclaredMethod("getConnection");
        // 判斷是否存在目前註解
        if (method.isAnnotationPresent(JDBCInfo.class)) {
            // 獲取註解信息
            JDBCInfo jdbcInfo = method.getAnnotation(JDBCInfo.class);
            // 讀取註解屬性信息
            String driverClassName = jdbcInfo.driverClassName();
            String url = jdbcInfo.url();
            String username = jdbcInfo.username();
            String password = jdbcInfo.password();
            // Class類加載驅動
            Class.forName(driverClassName);
            // 返回連接對象
            return DriverManager.getConnection(url, username, password);
        }
        return null;
    }
}
  • 編寫一個測試類用於測試JDBCUtils工具類是否正確。
public class JDBCTest {
    public static void main(String[] args) throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "select * from products";
        PreparedStatement statement = conn.prepareStatement(sql);
        ResultSet rs = statement.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("name") + "," + rs.getDouble("price"));
        }

        rs.close();
        statement.close();
        conn.close();
    }
}

轉載說明:請註明作者和原文鏈接,謝謝!

發佈了23 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章