官方文檔定義:注解是一系列元數(shù)據(jù),它提供數(shù)據(jù)用來(lái)解釋程序代碼,但是注解并非是所解釋的代碼本身的一部分得哆。注解對(duì)于代碼的運(yùn)行效果沒有直接影響。
簡(jiǎn)單來(lái)說(shuō)哟旗,你可以理解為一種標(biāo)簽贩据,例如一提到自己心中的女神,腦海中自然會(huì)為她貼上"beautiful"這個(gè)標(biāo)簽闸餐,但是你的這個(gè)"beautiful"標(biāo)簽并不屬于她本身饱亮,你的這個(gè)標(biāo)簽對(duì)她來(lái)說(shuō)沒有直接影響,你只是條單純的舔狗而已......
好吧舍沙,回到正題近上,注解存在的意義是什么呢?
注解的用處主要如下:
- 提供信息給編譯器: 編譯器可以利用注解來(lái)探測(cè)錯(cuò)誤和警告信息
- 編譯階段時(shí)的處理: 軟件工具可以用來(lái)利用注解信息來(lái)生成代碼拂铡、Html文檔或者做其它相應(yīng)處理壹无。
- 運(yùn)行時(shí)的處理: 某些注解可以在程序運(yùn)行的時(shí)候接受代碼的提取
還是回到官方文檔的解釋上,注解主要針對(duì)的是編譯器和其它工具軟件(SoftWare tool)感帅。
當(dāng)開發(fā)者使用了Annotation 修飾了類斗锭、方法、Field 等成員之后留瞳,這些 Annotation 不會(huì)自己生效拒迅,必須由開發(fā)者提供相應(yīng)的代碼來(lái)提取并處理 Annotation 信息。這些處理提取和處理 Annotation 的代碼統(tǒng)稱為 APT(Annotation Processing Tool)她倘。
所以呢璧微,注解是給編譯器或APT用的
例如:
public @interface AnnotationTest() {
}
@AnnotationTest
public void Test() {
}
上述代碼簡(jiǎn)單來(lái)說(shuō)就是把AnnotationTest作為一個(gè)標(biāo)簽貼到Test類上面去了
但是實(shí)際上并沒有什么卵用,因?yàn)楦緵]有實(shí)現(xiàn)注解功能
想要注解能夠正常工作硬梁,還是要依賴于元注解這個(gè)東西
元注解是一種基本注解前硫,可以注解到其他的注解上面去
主要有@Retention、@Document荧止、@Target屹电、@Inherited阶剑、@Repeatable5種
@Retention
當(dāng)它應(yīng)用到一個(gè)注解上的時(shí)候,說(shuō)明了這個(gè)注解的存活時(shí)間
取值如下:
- RetentionPolicy.SOURCE: 注解只在源碼階段保留危号,在編譯器進(jìn)行編譯時(shí)它將被丟棄忽視牧愁。
- RetentionPolicy.CLASS: 注解只被保留到編譯進(jìn)行的時(shí)候,它并不會(huì)被加載到 JVM 中外莲。
- RetentionPolicy.RUNTIME: 注解可以保留到程序運(yùn)行的時(shí)候猪半,它會(huì)被加載進(jìn)入到 JVM 中,所以在程序運(yùn)行時(shí)可以獲取到它們偷线。
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationTest() {
}
@Document
將被標(biāo)注的注解生成到j(luò)avadoc中
@Target
用于指定被此元注解標(biāo)注的注解可以標(biāo)注的程序元素
可以看一下@Target的源碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
可以看到有一個(gè)value屬性磨确,返回一個(gè)枚舉ElementType類型的數(shù)組,這個(gè)數(shù)組的值就代表了可以使用的程序元素声邦。
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Target取值如下
- ElementType.TYPE 可以給一個(gè)類型進(jìn)行注解乏奥,比如類、接口亥曹、枚舉
- ElementType.FIELD 可以給屬性進(jìn)行注解
- ElementType.METHOD 可以給方法進(jìn)行注解
- ElementType.PARAMETER 可以給一個(gè)方法內(nèi)的參數(shù)進(jìn)行注解
- ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
- ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
- ElementType.ANNOTATION_TYPE 可以給一個(gè)注解進(jìn)行注解
- ElementType.PACKAGE 可以給一個(gè)包進(jìn)行注解
- ElementType.TYPE_PARAMETER標(biāo)明注解可以用于類型參數(shù)聲明(1.8新加入)
-
ElementType.TYPE_USE類型使用聲明(1.8新加入)邓了,可以標(biāo)注除class之外的任意類型
當(dāng)注解未指定Target值時(shí),則此注解可以用于任何元素之上歇式,多個(gè)值使用{}包含并用逗號(hào)隔開
@Target(value = {ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.TYPE_PARAMETER})
@Inherited
其讓被修飾的注解擁有被繼承的能力,但是它并不是說(shuō)注解本身可以繼承驶悟,而是說(shuō)如果一個(gè)父類被 @Inherited 注解過(guò)的注解進(jìn)行注解的話胡野,那么如果它的子類沒有被任何注解應(yīng)用的話材失,那么這個(gè)子類就繼承了父類的注解。
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(value = {ElementType.TYPE})
@Inherited
public @interface TestAnnotation {}
@TestAnnotation
public class A {}
public class B extends A{}
也就是說(shuō)B也擁有TestAnnotation 這個(gè)注解
@Repeatable
表示注解可以多次應(yīng)用硫豆,通常是注解的值可以取多個(gè)
一個(gè)很簡(jiǎn)單的例子:我是一個(gè)程序員龙巨,同時(shí)也是美食家和攝影師
@interface Live {
People[] value();
}
@Repeatable(Live.class)
@interface People {
String role() default "";
}
@People(role = "programmer")
@People(role = "gastronome")
@People(role = "photographer")
public class Me {
}
上述代碼通過(guò)@Repeatable注解了People ,而@Repeatable 后面括號(hào)中的Live類相當(dāng)于一個(gè)容器注解(A fucking new concept)
容器注解:就是用來(lái)存放其它注解的地方,它本身也是一個(gè)注解
按java規(guī)定熊响,容器注解里面必須要有一個(gè)value屬性旨别,屬性類型是一個(gè)被 @Repeatable 注解過(guò)的注解數(shù)組
注解的屬性也叫成員變量,注解只有成員變量汗茄,沒有方法秸弛,而且成員變量在注解的定義中以“無(wú)形參的方法”的形式來(lái)聲明,方法名定義了成員變量的名字洪碳,返回值定義了成員變量的類型递览。
在注解中定義屬性時(shí)它的類型必須是 8 種基本數(shù)據(jù)類型外加 類、接口瞳腌、注解及它們的數(shù)組
注解中屬性可以有默認(rèn)值绞铃,默認(rèn)值需要用 default 關(guān)鍵值指定,這樣做的好處是可以省略賦值操作
@People()
public class Me {}
如果成員變量?jī)H有一個(gè)名字叫做value的屬性時(shí)嫂侍,你甚至可以這樣寫
@People("programmer")
public class Me {}
如果沒有成員變量的話儿捧,括號(hào)都可以省略喲荚坞!
@People
public class Me {}
Java語(yǔ)言本身也提供了預(yù)置的注解
@Deprecated
這個(gè)元素是用來(lái)標(biāo)記過(guò)時(shí)的元素,會(huì)有一條橫線菲盾,這是編譯器識(shí)別后的提醒效果
@SuppressWarnings
阻止警告的意思颓影。之前說(shuō)過(guò)調(diào)用被 @Deprecated 注解的方法后,編譯器會(huì)警告提醒懒鉴,而有時(shí)候開發(fā)者會(huì)忽略這種警告瞭空,他們可以在調(diào)用的地方通過(guò) @SuppressWarnings 達(dá)到目的。
@SafeVarargs
參數(shù)安全類型注解疗我。它的目的是提醒開發(fā)者不要用參數(shù)做一些不安全的操作,它的存在會(huì)阻止編譯器產(chǎn)生 unchecked 這樣的警告咆畏。它是在 Java 1.7 的版本中加入的。
@FunctionalInterface
函數(shù)式接口注解吴裤,這個(gè)是 Java 1.8 版本引入的新特性旧找。函數(shù)式編程很火,所以 Java 8 也及時(shí)添加了這個(gè)特性麦牺。
函數(shù)式接口 (Functional Interface) 就是一個(gè)具有一個(gè)方法的普通接口钮蛛。
PS:函數(shù)式接口標(biāo)記有什么用,這個(gè)原因是函數(shù)式接口可以很容易轉(zhuǎn)換為 Lambda 表達(dá)式剖膳。
想必上面的理論知識(shí)都能很快理解吧魏颓,接下來(lái)就手撕代碼吧
注解只是給我們想要的東西去貼上一個(gè)標(biāo)簽,那么怎么去閱讀標(biāo)簽?zāi)兀?br>
注解通過(guò)反射獲取
查看一下Class.java源碼吱晒,可以找到isAnnotationPresent()這個(gè)方法,用來(lái)判斷它是否應(yīng)用了某個(gè)注解
/**
* {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.5
*/
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}
上面代碼可以看出Annotation接口是所有注解的父接口
此外甸饱,可以通過(guò)getAnnotation(Class<A> annotationClass())獲取指定類型的Annotation對(duì)象
/**
* @throws NullPointerException {@inheritDoc}
* @since 1.5
*/
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
而getAnnotations()可以獲取到這個(gè)元素上的所有類型的Annotation對(duì)象
/**
* @since 1.5
*/
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(annotationData().annotations);
}
getDeclaredAnnotation(Class<A> annotationClass)返回該元素上存在的直接修飾該元素的指定類型的注解,如果不存在則返回null.
/**
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
@SuppressWarnings("unchecked")
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().declaredAnnotations.get(annotationClass);
}
getDeclaredAnnotations()返回該元素上存在的直接修飾該元素的所有注解
/**
* @since 1.5
*/
public Annotation[] getDeclaredAnnotations() {
return AnnotationParser.toArray(annotationData().declaredAnnotations);
}
okay,枯燥無(wú)聊的源碼還有很多仑濒,例如getDeclaredAnnotationsByType()叹话、getAnnotationsByType()等等,接下來(lái)用一個(gè)簡(jiǎn)單的例子通過(guò)反射來(lái)獲取Annotation的屬性
首先墩瞳,定義一個(gè)注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default 1;
public String name() default "Kris";
}
然后呢
@TestAnnotation(id = 666,name = "Kris")
public class Me {
public static void main(String[] args) {
boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("name:"+testAnnotation.name());
}
}
}
okay,接下來(lái)執(zhí)行這個(gè)程序就可以發(fā)現(xiàn):
Amazing!!!
那么驼壶,繼續(xù)加把力唄!
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface A {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface B {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface C {
}
@TestAnnotation(id = 666,name = "Kris")
public class Me {
@A(value = "This is A!")
int a;
@B
@C
public void methodTest() {
}
public static void main(String[] args) {
boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("name:"+testAnnotation.name());
}
try {
Field a = Me.class.getDeclaredField("a");
a.setAccessible(true);
A annotation = a.getAnnotation(A.class);
if (annotation != null) {
System.out.println("A value:"+annotation.value());
}
Method methodTest = Me.class.getDeclaredMethod("methodTest");
if (methodTest != null) {
Annotation[] ans = methodTest.getAnnotations();
for (int i=0;i<ans.length;i++) {
System.out.println("methodTest Annotation:"+ans[i].annotationType().getSimpleName());
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
顯而易見的得到結(jié)果:
綜上所述喉酌,只要通過(guò)給元素添加注解热凹,再利用反射就可以獲取注解啦,但是獲取到有什么用呢泪电?下面就通過(guò)數(shù)據(jù)庫(kù)SQL創(chuàng)建語(yǔ)句實(shí)例來(lái)講解一下APT(Annotation Process Tool)吧般妙。
@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 false;
boolean unique() default false;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraint() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
String name() default "";
int value() default 0;
Constraints constraint() default @Constraints;
}
@DBTable(name = "MEMBER")
public class Member {
@SQLString(name = "ID",value = 50,constraint = @Constraints(primaryKey = true))
private int id;
@SQLString(name = "NAME",value = 30)
private String name;
@SQLInteger(name = "AGE")
private int age;
@SQLString(name = "DESCRIPTION",value = 150,constraint = @Constraints(allowNull = true))
private String description;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TableCreator {
public static void main(String[] args) {
String[] arg={"AnnotationTest.SQLAnnotation.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " +
className + " is :\n" + createTableSql(className));
}
}
private static String createTableSql(String className) {
Class<?> cl = null;
DBTable dbTable = null;
try {
cl = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (cl != null) {
dbTable = cl.getAnnotation(DBTable.class);
}
if (dbTable == null) {
System.out.println("No DBTable Annotations int class!");
return null;
}
String tableName = dbTable.name();
if (tableName.length() == 0) {
tableName = cl.getName().toUpperCase();
}
List<String> columnDefs = new ArrayList<>();
for (Field field : cl.getDeclaredFields()) {
String columnName = null;
if (field.isAnnotationPresent(SQLInteger.class)) {
SQLInteger sqlInteger = (SQLInteger) field.getDeclaredAnnotation(SQLInteger.class);
if (sqlInteger.name().length() == 0) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlInteger.name();
}
columnDefs.add(columnName + " INT" + getConstraints(sqlInteger.constraint()));
} else if (field.isAnnotationPresent(SQLString.class)) {
SQLString sqlString = (SQLString) field.getDeclaredAnnotation(SQLString.class);
if (sqlString.name().length() == 0) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlString.name();
}
columnDefs.add(columnName+" VARCHAR(" + sqlString.value() + ")" + getConstraints(sqlString.constraint()));
}
}
StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
for (String columnDef : columnDefs) {
createCommand.append("\n " + columnDef + ",");
}
return createCommand.substring(0,createCommand.length()-1)+");";
}
private static String getConstraints(Constraints con) {
StringBuilder constraints = new StringBuilder();
if (!con.allowNull()) {
constraints.append(" NOT NULL");
}
if (con.primaryKey()) {
constraints.append(" PRIMARY KEY");
}
if (con.unique()) {
constraints.append(" UNIQUE");
}
return constraints.toString();
}
}
OK,這樣就可以成功得通過(guò)APT得到所需要的SQL語(yǔ)句啦~