java 深入理解註解

註解(也被稱爲元數據),爲我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便的使用這些數據。
註解在一定程度上是把元數據和源代碼結合在一起。

java5.0內置了三種標準註解
@override:表示當前的方法將覆蓋超類中的方法。
@Depercated:如果程序員使用了該註解,那麼編譯器會發出警告信息。
@SuppressWarnings:關閉不當的編譯器警告信息。

一、基本語法

註解的定義和接口類似,和其他java類一樣也會被編譯成class文件。

package annotation;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}

1. 元註解

定義註解時需要會一些元註解(meta-annotation),Java5.0定義了4個標準的meta-annotation類型

元註解 說明
@Target 表示該註解可以用到哪些地方,可以指定一個enum的值,也可以用逗號分隔的方式指定多個值(如果想要將註解應用於所有的ElementType,可以省去@Target元註解,不過這種情況不常見),可以選擇的ElementType參數有:
CONSTRUCTOR:構造器的聲明
FIELD:域聲明(包括enum實例)
LOCAL_VARIABLE:局部變量聲明
METHOD:方法聲明
PACKAGE:包聲明
PARAMMETER:參數聲明
TYPE:類、接口(包括註解類型)或enum聲明
@Retention 表示需要在什麼級別保存該註解信息。可選參數RetentionPolicy參數包括:
SOURCE:註解將被編譯器丟棄。
CLASS:註解在class文件中可用,但在VM中將被丟棄。
RUNTIME:VM將在運行期也保留註解,因此可以通過反射機制讀取註解的信息。
@Documented 將註解包含在javadoc中
@Inherited 允許子類繼承父類的註解


2. 註解元素:

package annotation;
import java.lang.annotation.*;

@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {

    public int id();
    public String description() default "no description";
}

標籤@UseCase由UseCase.java定義,其中包含int類型元素id和String類型元素description。

註解可用的類型如下:
- 所有基本類型(int、float、boolean等)
- String類型
- enum類型
- Annotation類型
- 以上類型的數組
注:也不允許使用任何包裝類型

沒有元素的註解被稱爲標記註解(marker annotation),例如上例中的@Test。

3. 默認值限制

元素不能有不確定的值,也就是說元素要麼有默認值,要麼在使用註解時提供元素的值。
對於非基本類型的值,無論是源代碼聲明時還是在註解接口中定義默認值時都不能使用null。這個約束是的處理器很難表現一個元素的存在或缺失狀態,因爲在每個元素的註解聲明中,所有的元素都存在,並且都具有相應的值,爲了繞開這個約束,我們只能自己定義一些特殊的值,如空字符串或負數等以此表示該元素不存在。

package annotation;
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Null {
    public int id() default 0;
    public String description() default "";
}

二、註解處理器

如果沒有用來讀取註解的工具,那註解也不會比註釋更有用。使用註解的過程,最重要的就是編寫和使用註解處理器
Java5.0擴展了反射機制的API,以幫助程序員構造這類工具。
下面是一個簡單的註解處理器,它將通過反射機制讀取PasswordUtils類中的標記。

package annotation;

import java.lang.reflect.Method;
import java.util.*;

public class UseCaseTracker {
    public static void trackerUseCase(List<Integer> useCases, Class<?> c) {
        for (Method m : c.getDeclaredMethods()) {
            UseCase uc = m.getAnnotation(UseCase.class);
            if (uc != null) {
                System.out.println("Found use case:" + uc.id() + "  " + uc.description());
                useCases.remove(new Integer(uc.id()));
            }
        }
        for (Integer u : useCases) {
            System.out.println("Missing use case-" + u);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<Integer>();
        Collections.addAll(useCases, 47, 48, 49, 50);
        UseCaseTracker.trackerUseCase(useCases, PasswordUtils.class);
    }
}

這個程序用到了兩個反射的方法:getDeclaredMethods()和getAnnotation(),它們都屬於AnnotatedElement接口(Class、Field、Method等都實現了該接口)。getAnnotation():返回指定類型的註解對象,這裏是UseCase。如果方法上沒有該類型的註解,那麼返回null值。然後我們通過id()和description()方法從UseCase對象中獲取元素的值。

package annotation;

import java.util.List;

public class PasswordUtils {

    @UseCase(id=47, description="Passwords must contain at least one numeric")
    public boolean validatePassword(String password) {
        return (password.matches("\\w*\\d\\w*"));
    }

    @UseCase(id=48)
    public String encryptPassword(String password) {
        return new StringBuilder(password).reverse().toString();
    }

    @UseCase(id=49, description="New passwords can not equal previously used ones")
    public boolean checkForNewPassword(List<String> prePasswords, String password) {
        return !prePasswords.contains(password);
    }
}

運行結果:
Found use case:47 Passwords must contain at least one numeric
Found use case:48 no description
Found use case:49 New passwords can not equal previously used ones
Missing use case-50

encryptPassword沒有指定description元素的值,因此處理器處理它對應的註解時通過description()方法獲取的是默認值no description。

三、例子

假設你希望提供簡單的對象/關係映射功能,能夠自動生成數據庫表,用來存儲JavaBean對象。你可以使用XML描述文件,指明類的名字、每個成員以及數據庫映射的相關信息。然而如果使用註解的話就可以將所有信息都保存到JavaBean源文件中。
爲此我們需要一些新的註解

DBTable.java告訴處理器需要給我生成一個數據庫表

package annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {

    public String name() default "";
}

注:@DBTable有一個元素name,通過這個元素爲處理器創建數據庫表提供表名。
下面爲修飾JavaBean域提供的註解

package annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Contraints {

    boolean primaryKey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}
package annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {

    int value() default 0;
    String name() default "";
    Contraints contrants() default @Contraints;
}
package annotation;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {

    String name() default "";
    Contraints contrants() default @Contraints;
}

上面這兩個SQL類型具有name()和contrants()元素,後者用到了嵌套註解的功能

package annotation;

@DBTable(name="MEMBER")
public class Member {

    @SQLString(30) String firstName;
    @SQLString(50) String lastName;
    @SQLInteger Integer age;
    @SQLString(value=30, contrants=@Contraints(primaryKey=true)) 
    String handle;
    static int memberCount;

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getHandle() {
        return handle;
    }
    public void setHandle(String handle) {
        this.handle = handle;
    }

    @Override
    public String toString() {
        return handle;
    }
}

可以使用多種不同的方式來定義自己的註解,上面只定義了String和Integer的註解,還有很多其他類型如果一一定義會很繁瑣,這時可以使用@TableColumn單一的註解類,它包含一個enum元素,該枚舉類定義了STRING,INTEGER,FLOAT…等,這就消除了所有SQL類型都需要定義一個註解類的負擔。
一個目標可以使用多個註解,但是同一註解不能重複使用。

package annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class TableCreator {

    public static void main(String[] args) throws ClassNotFoundException {
        /*if (args.length < 1) {
            System.out.println("arguments: annotation classes");
            System.exit(0);
        }*/
        args = new String[]{"annotation.Member"};
        for (String className : args) {
            Class<?> c = Class.forName(className);
            DBTable table = c.getAnnotation(DBTable.class);
            if (table == null) {
                System.out.println("NO DBTable in classes: " + className);
                continue;
            }
            String tableName = table.name();
            List<String> columnDefs = new ArrayList<String>();
            for (Field f : c.getDeclaredFields()) {
                String columnName = null;
                Annotation[] annos = f.getAnnotations();
                if (annos.length < 1) 
                    continue;
                if (annos[0] instanceof SQLString) {
                    SQLString sqlString = (SQLString) annos[0];
                    columnName = sqlString.name();
                    if (columnName.length() < 1) {
                        columnName = f.getName().toUpperCase();
                    }
                    columnDefs.add(columnName + " VARCHAR(" + sqlString.value() + ")" + getContraints(sqlString.contrants()));
                }
                if (annos[0] instanceof SQLInteger) {
                    SQLInteger sqlInt = (SQLInteger) annos[0];
                    columnName = sqlInt.name();
                    if (columnName.length() < 1) {
                        columnName = f.getName().toUpperCase();
                    }
                    columnDefs.add(columnName + " INT" + getContraints(sqlInt.contrants()));
                }
            }
            StringBuffer buf = new StringBuffer("CREATE TABLE " + tableName + "(\n");
            for (String column : columnDefs) {
                buf.append(column + ",\n");
            }
            buf.substring(0, buf.length()-1);
            buf.append(");");
            System.out.println(buf);
        }
    }

    /**
     * 獲取約束
     * @param contrants
     * @return
     */
    private static String getContraints(Contraints con) {
        String contraints = "";
        if (con.primaryKey()) 
            contraints += " PRIMARY KEY";
        if (con.allowNull()) 
            contraints += " NULL";
        if (con.unique())
            contraints += " UNIQUE";
        return contraints;
    }
}

運行結果:

CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30) NULL,
LASTNAME VARCHAR(50) NULL,
AGE INT NULL,
HANDLE VARCHAR(30) PRIMARY KEY NULL,
);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章