Java注解库_Java 注解详解
本文部分摘自 On Java 8
基本語(yǔ)法
注解是 Java 5 所引入的眾多語(yǔ)言變化之一,是附加在代碼中的一些元信息,用于一些工具在編譯、運(yùn)行時(shí)進(jìn)行解析和使用,起到說明、配置的功能。注解不會(huì)也不能影響代碼的實(shí)際邏輯,僅僅起到輔助性的作用,包含在 java.lang.annotation 包中
注解的語(yǔ)法十分簡(jiǎn)單,只要在現(xiàn)有語(yǔ)法中添加 @ 符號(hào)即可,java.lang 包提供了如下五種注解:
@Override
表示當(dāng)前的方法定義將覆蓋基類的方法,如果你不小心把方法簽名拼錯(cuò)了,編譯器就會(huì)發(fā)出錯(cuò)誤提示
@Deprecated
如果使用該注解的元素被調(diào)用,編譯器就會(huì)發(fā)出警告信息,表示不鼓勵(lì)程序員使用
@SuppressWarnings
關(guān)閉不當(dāng)?shù)木幾g器警告信息
@SafeVarargs
禁止對(duì)具有泛型可變參數(shù)的方法或構(gòu)造函數(shù)的調(diào)用方發(fā)出警告
@FunctionalInterface
聲明接口類型為函數(shù)式接口
定義注解
注解的定義看起來和接口的定義很像,事實(shí)上它們和其他 Java 接口一樣,也會(huì)被編譯成 class 文件
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {}
除開 @ 符號(hào), @Test 的定義看起來更像一個(gè)空接口。注解的定義也需要一些元注解,元注解用于注解其他的注解
注解
解釋
@Target
表示注解可以用于哪些地方??赡艿?ElementType 參數(shù)包括:
CONSTRUCTOR:構(gòu)造器的聲明
FIELD:字段聲明(包括 enum 實(shí)例)
LOCAL_VARIABLE:局部變量聲明
METHOD:方法聲明
PACKAGE:包聲明
PARAMETER:參數(shù)聲明
TYPE:類、接口(包括注解類型)或者 enum 聲明
@Retention
表示注解信息保存的時(shí)長(zhǎng)??蛇x的 RetentionPolicy 參數(shù)包括:
SOURCE:注解將被編譯器丟棄
CLASS:注解在 class 文件中可用,但是會(huì)被 VM 丟棄
RUNTIME:VM 將在運(yùn)行期也保留注解,因此可以通過反射機(jī)制讀取注解的信息
@Documented
將此注解保存在 Javadoc 中
@Inherited
允許子類繼承父類的注解
@Repeatable
允許一個(gè)注解可以被使用一次或者多次(Java8)
不包含任何元素的注解稱為標(biāo)記注解,上例中的 @Test 就是標(biāo)記注解。注解通常也會(huì)包含一些表示特定值的元素,當(dāng)分析處理注解的時(shí)候,程序可以利用這些值。注解的元素看起來就像接口的方法,但可以為其指定默認(rèn)值
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String description() default "no description";
}
....
public class TestUtils {
// 在方法上使用注解 @TestAnnotation
@UseCase(id = 47, description = "description")
public void test() {
...
}
}
注解元素可用的類型如下所示,如果使用了其他類型,編譯器就會(huì)報(bào)錯(cuò):
所有基本類型(int、float、boolean 等)
String
Class
enum
Annotation
以上類型的數(shù)組
如果沒有給出 description 的值,在分析處理這個(gè)類的時(shí)候會(huì)使用該元素的默認(rèn)值。元素的默認(rèn)值不能有不確定的值,也就是說,元素要么有默認(rèn)值,要么就在使用注解時(shí)提供元素的值
這里還有另外一個(gè)限制:任何非基本類型的元素,無(wú)論是在源代碼聲明時(shí)還是在注解接口中定義默認(rèn)值時(shí),都不能使用 null 作為值。如果我們希望表現(xiàn)一個(gè)元素的存在或者缺失的狀態(tài),可以自定義一些特殊的值,比如空字符串或者負(fù)數(shù)用于表達(dá)某個(gè)元素不存在
注解不支持繼承,你不能使用 extends 關(guān)鍵字來繼承 @interface
注解處理器
如果沒有用于讀取注解的工具,那么注解不會(huì)比注釋更有用。使用注解中一個(gè)很重要的作用就是創(chuàng)建與使用注解處理器。Java 拓展了反射機(jī)制的 API 用于幫助你創(chuàng)造這類工具。同時(shí)他還提供了 javac 編譯器鉤子在編譯時(shí)使用注解
下面是一個(gè)非常簡(jiǎn)單的注解處理器,我們用它來讀取被注解的 TestUtils 類,并且使用反射機(jī)制來尋找 @TestAnnotation 標(biāo)記
public class TestAnnotationTracker {
public static void trackTestAnnotation(Class> cl) {
for(Method m : cl.getDeclaredMethods()) {
TestAnnotation ta = m.getAnnotation(TestAnnotation.class);
if(ta != null) {
System.out.println(ta.id() + "\n " + ta.description());
}
}
}
public static void main(String[] args) {
trackTestAnnotation(TestUtils.class);
}
}
這里用到了兩個(gè)反射的方法:getDeclaredMethods() 和 getAnnotation(),getAnnotation() 方法返回指定類型的注解對(duì)象,在本例中就是 TestAnnotation,如果被注解的方法上沒有該類型的注解,返回值就為 null。通過調(diào)用 id() 和 description() 方法來提取元素值
使用注解實(shí)現(xiàn)對(duì)象 - 數(shù)據(jù)庫(kù)映射
當(dāng)有些框架需要一些額外的信息才能與你的源代碼協(xié)同工作,這種情況下注解就會(huì)變得十分有用。自定義例如對(duì)象/關(guān)系映射工具(Hibernate 和 MyBatis)通常都需要 XML 描述文件,而這些文件脫離于代碼之外。除了定義 Java 類,程序員還必須重復(fù)的提供某些信息,而例如類名和包名等信息已經(jīng)在原始類中提供過了,經(jīng)常會(huì)導(dǎo)致代碼和描述文件的同步問題
假設(shè)你現(xiàn)在想提供一些基本的對(duì)象/關(guān)系映射功能,能夠自動(dòng)生成數(shù)據(jù)庫(kù)表。你可以使用 XML 描述文件來指明類的名字、每個(gè)成員以及數(shù)據(jù)庫(kù)映射的相關(guān)信息。但是,通過使用注解,你可以把所有信息都保存在 JavaBean 源文件中。為此你需要一些用于定義數(shù)據(jù)庫(kù)表名稱、數(shù)據(jù)庫(kù)列以及將 SQL 類型映射到屬性的注解
首先創(chuàng)建一個(gè)用來映射數(shù)據(jù)庫(kù)表的注解,用來修飾類、接口(包括注解類型)或者 enum 聲明
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name() default "";
}
如下是修飾字段的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
@Constraints 代表了數(shù)據(jù)庫(kù)通常提供的約束的一小部分,primaryKey(),allowNull() 和 unique() 元素都提供了默認(rèn)值,大多數(shù)情況下,注解的使用者都不需要輸入太多東西
另外兩個(gè) @interface 定義的是 SQL 類型。如果希望這個(gè)框架更有價(jià)值的話,我們應(yīng)該為每個(gè) SQL 類型都定義相應(yīng)的注解。不過作為示例,兩個(gè)元素足夠了。這些 SQL 類型具有 name() 元素和 constraints() 元素。后者利用了嵌套注解的功能,將數(shù)據(jù)庫(kù)列的類型約束信息嵌入其中。注意 constraints() 元素的默認(rèn)值是 @Constraints,沒有在括號(hào)中指明 @Constraints 元素的值,因此,constraints() 的默認(rèn)值為所有元素都為默認(rèn)值。如果要使得嵌入的 @Constraints 注解中的 unique() 元素為 true,并作為 constraints() 元素的默認(rèn)值,你可以像如下定義:
public @interface Uniqueness {
Constraints constraints() default @Constraints(unique = true);
}
下面是一個(gè)簡(jiǎn)單的,使用了如上注解的類
@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 reference;
static int memberCount;
public String getReference() { return reference; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
@Override
public String toString() { return reference; }
public Integer getAge() { return age; }
}
類注解 @DBTable 注解給定了元素值 MEMBER,它將會(huì)作為表的名字。類的屬性 firstName 和 lastName 都被注解為 @SQLString 類型并且給了默認(rèn)元素值分別為 30 和 50,并在嵌入的 @Constraint 注解中設(shè)定 primaryKey 元素的值
下面是一個(gè)注解處理器的例子,它將讀取一個(gè)類文件,檢查上面的數(shù)據(jù)庫(kù)注解,并生成用于創(chuàng)建數(shù)據(jù)庫(kù)的 SQL 命令:
public class TableCreator {
public static void generateSql(String[] classnames) throws Exception {
for (String className : classnames) {
Class> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
String tableName = dbTable.name();
// 如果表名為空字符串,則使用類名
if (tableName.length() < 1) {
tableName = cl.getName().toUpperCase();
}
List columnDefs = new ArrayList<>();
for (Field field : cl.getDeclaredFields()) {
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
// 該屬性不是列
if (anns.length < 1) {
continue;
}
// 處理整數(shù)類型
if (anns[0] instanceof SQLInteger) {
SQLInteger sInt = (SQLInteger) anns[0];
// 如果列名為空字符串,則使用屬性名
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()));
}
// 構(gòu)造并輸出 sql 字符串
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);
}
}
}
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;
}
}
編譯時(shí)注解處理
當(dāng) @Retention 的 RetentionPolicy 參數(shù)被標(biāo)注為 SOURCE 或 CLASS,此時(shí)你無(wú)法通過反射去獲取注解信息,因?yàn)樽⒔庠谶\(yùn)行期是不存在的。使用 javac 可以創(chuàng)建編譯時(shí)注解處理器,在編譯時(shí)掃描和處理注解。你可以自定義注解,并注冊(cè)到對(duì)應(yīng)的注解處理器。注解處理器可以生成 Java 代碼,這些生成的 Java 代碼會(huì)組成新的 Java 源文件,但不能修改已經(jīng)存在的 Java 類,例如向已有的類中添加方法。如果你的注解處理器創(chuàng)建了新的源文件,在新一輪處理中注解處理器會(huì)檢查源文件本身,在檢測(cè)一輪之后持續(xù)循環(huán),直到不再有新的源文件產(chǎn)生,然后編譯所有的源文件
我們來編寫一個(gè)簡(jiǎn)單的注解處理器,如下是注解的定義
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE,
ElementType.PACKAGE, ElementType.FIELD,
ElementType.LOCAL_VARIABLE})
public @interface Simple {
String value() default "-default-";
}
@Retention 的參數(shù)為 SOURCE,這意味著注解不會(huì)存留在編譯后的 class 文件,因?yàn)檫@對(duì)應(yīng)編譯時(shí)處理注解是沒有必要的,在這里,javac 是唯一有機(jī)會(huì)處理注解的方式
package annotations.simplest;
@Simple
public class SimpleTest {
@Simple
int i;
@Simple
public SimpleTest() {}
@Simple
public void foo() {
System.out.println("SimpleTest.foo()");
}
@Simple
public void bar(String s, int i, float f) {
System.out.println("SimpleTest.bar()");
}
@Simple
public static void main(String[] args) {
@Simple
SimpleTest st = new SimpleTest();
st.foo();
}
}
運(yùn)行 main 方法,程序就會(huì)開始編譯,如下是一個(gè)簡(jiǎn)單的處理器,作用就是把注解相關(guān)的信息打印出來
package annotations.simplest;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import java.util.*;
@SupportedAnnotationTypes("annotations.simplest.Simple")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SimpleProcessor extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment env) {
for(TypeElement t : annotations) {
System.out.println(t);
}
for(Element el : env.getElementsAnnotatedWith(Simple.class)) {
display(el);
}
return false;
}
private void display(Element el) {
System.out.println("==== " + el + " ====");
System.out.println(el.getKind() +// 返回此元素的種類,字段,方法,或是類
" : " + el.getModifiers() +// 返回此元素的修飾符
" : " + el.getSimpleName() +// 返回此元素的簡(jiǎn)單名稱
" : " + el.asType());// 返回此元素定義的類型
// 如果元素為CLASS類型,動(dòng)態(tài)向下轉(zhuǎn)型為更具體的元素類型,并打印相關(guān)信息
if(el.getKind().equals(ElementKind.CLASS)) {
TypeElement te = (TypeElement)el;
System.out.println(te.getQualifiedName());
System.out.println(te.getSuperclass());
System.out.println(te.getEnclosedElements());
}
// 如果元素為METHOD類型,動(dòng)態(tài)向下轉(zhuǎn)型為更具體的元素類型,并打印相關(guān)信息
if(el.getKind().equals(ElementKind.METHOD)) {
ExecutableElement ex = (ExecutableElement)el;
System.out.print(ex.getReturnType() + " ");
System.out.print(ex.getSimpleName() + "(");
System.out.println(ex.getParameters() + ")");
}
}
}
使用 @SupportedAnnotationTypes 和 @SupportedSourceVersion 注解來確定支持哪些注解以及支持的 Java 版本
注解處理器需要繼承抽象類 javax.annotation.processing.AbstractProcessor,唯一需要實(shí)現(xiàn)的方法就是 process(),這里是所有行為發(fā)生的地方。第一個(gè)參數(shù)獲取到此注解處理器所要處理的注解集合,第二個(gè)參數(shù)保留了剩余信息,這里我們所做的事情只是打印了注解(只存在一個(gè))。process() 中實(shí)現(xiàn)的第二個(gè)操作是循環(huán)所有被 @Simple 注解的元素,并且針對(duì)每一個(gè)元素調(diào)用 display() 方法。展示所有 Element 自身的基本信息
總結(jié)
以上是生活随笔為你收集整理的Java注解库_Java 注解详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 单例模式 泛型_设计模式之架构
- 下一篇: java getresourceasst