什么是注解
Java 注解(Annotation)又稱 Java 標注或元數(shù)據(jù),是 JDK5.0 引入的新特性钉疫,用于對代碼進行說明寞钥,可以對包、類陌选、接口理郑、字段、方法參數(shù)咨油、局部變量等進行注解您炉。例如我們常見的@Override、@Deprecated役电、@Test等赚爵。
簡單來說,可以把注解理解成代碼中的特殊標記,這些標記可以在編譯冀膝、類加載唁奢、運行時被讀取,并執(zhí)行相應的處理窝剖。通過注解麻掸,開發(fā)人員可以在不改變原有代碼情況下,在源代碼中嵌入補充信息赐纱。
注解的作用
- 生成文檔:通過代碼里標識的元數(shù)據(jù)生成javadoc文檔脊奋;
- 編譯檢查:通過代碼里標識的元數(shù)據(jù),讓編譯器在編譯期間進行檢查疙描;
- 編譯時動態(tài)處理:例如編譯時通過代碼里標識的元數(shù)據(jù)诚隙,動態(tài)生成代碼;
- 運行時動態(tài)處理:運行時通過代碼里標識的元數(shù)據(jù)動態(tài)處理起胰,例如使用反射注入實例
注解語法和使用
通常久又,注解可分為以下三類:
1. 元注解
元注解是用于定義注解的注解,java.lang.annotation提供了四種元注解效五,專門注解其他的注解:
- @Documented: 標明是否生成javadoc文檔
- @Retention: 標明注解被保留的階段地消,即什么時候使用該注解
public enum RetentionPolicy {
SOURCE, /* Annotation信息僅存在于編譯器處理期間,編譯器處理完之后就沒有該Annotation信息了 */
CLASS, /* 編譯器將Annotation存儲于類對應的.class文件中火俄,默認行為 */
RUNTIME /* 編譯器將Annotation存儲于class文件中犯建,并且可由JVM讀入 */
}
例如
@Override
, 當它修飾一個方法的時候瓜客,表明該方法是重寫父類的方法适瓦,編譯期間會進行語法檢查,但編譯器處理完后谱仪,@Override
就沒有任何作用玻熙。
- @Target: 標明注解使用的范圍,即該注解用于什么地方
public enum ElementType {
TYPE, /* 描述類疯攒、接口(包括注釋類型)或枚舉 */
FIELD, /* 描述成員變量嗦随、對象、屬性 */
METHOD, /* 用來描述方法 */
PARAMETER, /* 參數(shù)聲明 */
CONSTRUCTOR, /* 構造方法聲明 */
LOCAL_VARIABLE, /* 描述局部變量 */
ANNOTATION_TYPE, /* 注釋類型聲明 */
PACKAGE /* 包聲明 */
}
- @Inherited: 標明注解可繼承敬尺,是否允許子類繼承該注解
2. Java常用注解
@Override: 標明重寫某個方法
@Deprecated: 標明某個類或方法過時
@SuppressWarnings: 標明要忽略的警告枚尼,
當代碼中使用這些注解后,編譯器就會進行檢查砂吞。
例如署恍,這兩段代碼是@Override和@SuppressWarnings的Java源文件:
[Override.java]
@Target(ElementType.METHOD) /* 表明@Override用于描述方法 */
@Retention(RetentionPolicy.SOURCE) /* 表明@Override僅在于編譯器處理期間存在 */
public @interface Override {
}
[SuppressWarnings.java]
/* 表明@SuppressWarnings可以描述方法、字段蜻直、參數(shù)盯质、構造方法類等 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE) /* 表明@SuppressWarnings僅在于編譯器處理期間存在 */
public @interface SuppressWarnings {
String[] value();
}
@interface: 表示實現(xiàn)了 java.lang.annotation.Annotation 接口袁串,即該注解就是一個Annotation。
定義 Annotation 時呼巷,@interface 是必須的囱修。
注意:它和通常的 implemented 實現(xiàn)接口的方法不同,Annotation 接口的實現(xiàn)細節(jié)都由編譯器完成王悍。
通過 @interface 定義注解后破镰,該注解不能繼承其他的注解或接口。
@Override使用很常見配名,來看一個使用@SuppressWarnings的例子:
@SuppressWarnings("deprecation")
private void push() {
......
}
如果push() 方法是過期的方法啤咽,編譯時就會產(chǎn)生警告晋辆。而使用了 @SuppressWarnings(value={"deprecation"})后渠脉,編譯器會對"調(diào)用 push() 產(chǎn)生的警告"保持沉默。
3. 自定義注解
可以根據(jù)自己的需求定義注解瓶佳,如寫UT/IT case 時使用到的@Test芋膘,Google 開源依賴注入框架Dagger2中的@Inject、@Module等
@Test注解后霸饲,在運行該方法時为朋,測試框架會自動識別該方法并單獨調(diào)
注解處理器
如果沒有注解處理器,那注解跟注釋其實沒有什么區(qū)別厚脉,下面是Thinking Java的一個Demo习寸,使用注解創(chuàng)建數(shù)據(jù)表:
1. 定義用來創(chuàng)建表名的注解@DBTable:
//DBTable.java
@Target(ElementType.TYPE) //Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}
2. 定義用來約束的注解@Constraints:
//Constraints.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primarykey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
3. 定義聲明String類型的注解@SQLString:
//SQLString.java
@Target(ElementType.FIELD)
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}
4. 定義聲明Integer類型的注解@SQLInteger:
//SQLInteger.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
注意在SQLString.java和SQLInteger.java中constraints()元素的默認值就是@Constraints注解設定的默認值。如果現(xiàn)在要令嵌入的@Constraints注解中的unique()元素為true傻工, 并以此作為constraints元素的默認值霞溪,則需要如下定義該元素
//Uniqueness.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uniqueness {
Constraints constraints() default @Constraints(unique = true)
}
5. 使用以上這些注解,定義一個數(shù)據(jù)庫表:
//Member.java
@DBTable(name = "MEMBER")
public class Member {
@SQLString(30) String firstName;
@SQLString(50) String lastName;
@SQLInteger Integer age;
@SQLString(value = 30, constraints = @Constraints(primarykey = true))
String handle;
static int memberCount;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Integer getAge() {
return age;
}
public String getHandle() {
return handle;
}
public static int getMemberCount() {
return memberCount;
}
}
6. 實現(xiàn)注解處理器:
//TableCreator.java
{Args:com.sdkd.database.Member}
public class TableCreator {
private static String getConstraints(Constraints con) {
String constraints = "";
if(!con.allowNull()) constraints += "NOT NULL";
if(con.primarykey()) constraints += "PRIMARY KEY";
if(con.unique()) constraints += " UNIQUE";
return constraints;
}
public static void main(String[] args)throws Exception {
if(args.length < 1) {
System.out.println("arguments: annotated classes");
System.exit(0);
}
for(String className : args) {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
if(dbTable == null) {
System.out.println("No DBTable annotations in class" + className);
continue;
}
String tableName = dbTable.name();
//If the name is empty, use the Class name
if(tableName.length() < 1) {
tableName = cl.getName().toUpperCase();
}
List<String> columnDefs = new ArrayList<String>();
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
if (anns.length < 1) continue;
if (anns[0] instanceof SQLInteger) {
SQLInteger sInt = (SQLInteger) anns[0];
//Use field name if name not specified
if (sInt.name().length() < 1) {
columnName = field.getName().toUpperCase();
} else {
columnName = sInt.name();
}
columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
}
if (anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[0];
if (sString.name().length() < 1) {
columnName = field.getName().toUpperCase();
} else {
columnName = sString.name();
}
columnDefs.add(columnName + " VARCHAR(" +
sString.value() + ")" +
getConstraints(sString.constraints()));
}
}
StringBuilder createCommand = new StringBuilder(
"CREATE TABLE " + tableName + "("
);
for(String columnDef : columnDefs) {
createCommand.append("\n " + columnDef + ",");
String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
System.out.println("Table Creation SQL for " +
className + " is :\n" + tableCreate);
}
}
}
}
7. main()方法測試:
public class Test {
public static void main(String[] args) throws Exception {
String[] arg = {"com.sdkd.database.Member"};
new TableCreator().main(arg);
}
}
7. 輸出:
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT);
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30)PRIMARY KEY);