Java-Annotation(注解)
點擊上方藍色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達
作者 | 隨風(fēng)的海子
來源 | urlify.cn/euq6Br
一、注解的概念和作用
從JDK1.5開始,Java增加了對元數(shù)據(jù)(MetaData)的支持即Annotation(注解)其實就是代碼標(biāo)記,在不改變原有的代碼邏輯情況下通過注解可以讓代碼在編譯期或者運行期添加一下額外的處理,當(dāng)然這些處理也需要開發(fā)者自己來定義。
二、5個基本的注解
下面來看Java提供的5個基本的注解(其中好幾個我們都很熟悉了)
1. @Override
@Override 用于限定重寫父類的方法,它可以強制子類必須覆蓋父類的方法否則會引發(fā)編譯錯誤
代碼代碼示例
public class Fruit {
public void info() {
System.out.println("this is fruit info");
}
}
class Apple extends Fruit {
/**
* 這里雖然不加@Override注解也不會報任何編譯錯誤和運行時錯誤
* 但是如果info方法目的就是為了重寫父類的info方法,這里不加@Override
* 如果info寫錯了也不會報任何編譯異常,一旦寫錯了程序員也不會發(fā)現(xiàn)
*/
@Override
public void info() {
System.out.println("this is apple info");
}
}2. @Deprecated
用于標(biāo)識某個程序元素(類,方法)等已經(jīng)過時了,當(dāng)其他程序使用已經(jīng)過時的類和方法時編譯器將會給與警告,但是并不影響程序的運行。
3. @SuppressWarnings
表示被該注解修飾的程序元素以及該程序元素中的所有子元素取消顯示指定的編譯警告
@SuppressWarnings(value = "unchecked")
public class SuppressWarningsTest {
public static void main(String[] args) {
List<String> myList = new ArrayList<>();
}
}4. @Safevarargs
用于抑制堆污染警告
堆污染:當(dāng)把一個不帶泛型的對象賦給一個帶泛型的變量時就會發(fā)生堆污染警告
5. @FunctionalInterface
用于修飾函數(shù)式接口的,當(dāng)一個接口只有一個抽象方法時可以給該接口添加注解表示這時一個函數(shù)式接口
三、作用于注解的注解
JDK除了在java.lang包下提供了5個基本的注解之外,還在java.lang.annotation包下提供了5個Meta Annotation(元注解),其中4個用于修飾其他的注解,還有一個
@Repeatable為Java8新增的允許重復(fù)注解
1. @Retention
只能用于對其他注解的定義,指定被修飾的注解能夠保留多長時間,它包含一個
RetentionPolicy類型的成員變量,在使用該注解時必須給這個變量賦值,下面是賦值說明
| 屬性值 | 含義描述 |
|---|---|
| RetentionPolicy.SOURCE | 表示該注解只保留在源碼級別,編譯時會被編譯器直接丟棄 |
| RetentionPolicy.CLASS | 表示該注解會被編譯到class文件中,但是當(dāng)Java程序運行時會被丟棄,即JVM讀取不到該注解的信息(一般用于在編譯器通過改注解來添加額外的文件) |
| RetentionPolicy.RUNTIME | 表示該注解會被編譯到class文件中,當(dāng)Java程序運行時JVM可以通過反射讀取到該注解的信息 |
2. @target
只能用于對其他注解的定義,指定被修飾的注解能用于哪些程序單元,它包含一個
ElementType[]成員變量該變量的值如下
| 屬性值 | 含義描述 |
|---|---|
ElementType.TYPE | 表示該注解只能修飾類、接口、或者枚舉、以及注解類型 |
ElementType.FIELD | 表示該注解只能修飾成員變量 |
ElementType.METHOD | 表示該注解只能修飾方法 |
ElementType.PARAMETER | 表示該注解可以修飾參數(shù) |
ElementType.CONSTRUCTOR | 表示該注解只能修飾構(gòu)造器 |
ElementType.LOCAL_VARIABLE | 表示該注解只能修飾局部變量 |
ElementType.ANNOTATION_TYPE | 表示該注解只能修飾注解 |
ElementType.TYPE_PARAMETER | 表示該注解可以用于任何聲明類型的地方(1.8新增的) |
ElementType.TYPE_USE | 表示該注解可以用于任意使用類型的地方(1.8新增的) |
ElementType.PACKAGE | 表示該注解只能修飾包定義 |
3. @Documented
用于指定改注解修飾的注解類將會被javadoc工具提取成文檔
4. @Inherited
指定被它修飾的注解將會有繼承性,即如果某個類被@A注解修飾了,同事@A注解被@Inherited注解修飾,則這個類的子類也將默認被@A修飾
5. @Repeatable
java8新增的允重復(fù)注解(傳統(tǒng)的java語法不允許在同一段代碼使用一樣的兩個注解,如果強行使用只能使用一個注解容器來承載,@Repeatable就是為了解決這個問題的,其實其思想也是容器思想)
四、自定義注解
1. 定義注解
自定義注解的格式
public @interface 注解名{成員變量類型 成員變量名() default 默認值;}
其實和接口的定義非常類似
示例:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface DemoAnnotation {
String name() default "name";
}2. 注解解析使用
在使用了自定義注解修飾了類、方法、成員變量等之后這些注解不會自己生效,需要程序員自己提供相應(yīng)的注解處理工具來處理對應(yīng)的注解信息Java使用了Annotation接口來代表程序元素前面的注解,該接口是所有注解的父接口,同時Java1.5在java.lang.reflect包下新增了AnnotatedElement接口用于表示程序中可以接受注解的程序元素,這個接口的主要實現(xiàn)類有如下幾個:
| 實現(xiàn)類名 | 說明 |
|---|---|
| Class | 類定義 |
| Filed | 類的成員變量定義 |
| Method | 類的方法定義 |
| Package | 類的包定義 |
| Constructor | 構(gòu)造器定義 |
瞅瞅源碼:


那么使用方式就很舒服了:我們只需要通過反射獲取到對象的Class 便可以通過Class獲取對象上的注解信息了,而且通過Class也能獲取到Method和Constructor這樣也能獲取到對應(yīng)的注解,那關(guān)鍵問題就是AnnotateElement接口到底讓子類實現(xiàn)了哪些接口了?
| 方法名 | 作用說明 |
|---|---|
| getAnnotation(Class<A>annotationClass) | 獲取該程序元素上指定類型的注解,如果不存在返回null |
| getDeclaredAnnotation(Class<A>annotationClass) | Java8新增方法,嘗試獲取直接修飾該程序元素指定類型的注解(不包括繼承的),如果不存在返回null |
| getAnnotations() | 返回該程序元素上的所有注解 |
| getDeclaredAnnotations() | 返回直接修飾該程序元素的所有注解(不包括繼承的) |
| isAnnotationPresent(Class<?extends Annotation>annotationClass) | 判斷該程序元素上是否存在指定類型的注解 |
| getAnnotationsByType(Class<A> annotationClass) | 與上面的getAnnotation()方法類似,只是針對java8的重復(fù)注解功能,所以需要使用這個方法獲取修飾該程序元素指定類型的多個注解 |
| getDeclaredAnnotationsByType(Class<A> annotationClass) | 與上面的getDeclaredAnnotation方法類似,只是針對java8的重復(fù)注解功能,所以需要使用這個方法獲取修飾該程序元素指定類型的多個注解 |
代碼示例:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAnnotation {
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotationParent {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DemoAnnotation {
String name() default "name";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodParentAnnotation {
}
@ClassAnnotationParent
public class AnnotationParentTest {
@MethodParentAnnotation
public void infoParent(){
System.out.println("this is annotation parent test info");
}
}
@ClassAnnotation
public class AnnotationTest extends AnnotationParentTest {
@DemoAnnotation
public void info() {
System.out.println("this is Annotation test info ");
}
}
public class HaiziTest {
public static void main(String[] args) throws NoSuchMethodException {
AnnotationTest test = new AnnotationTest();
Class<? extends AnnotationTest> testClass = test.getClass();
//獲取類對象上的
Annotation[] annotations = testClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation = " + annotation);
}
Annotation[] declaredAnnotations = testClass.getDeclaredAnnotations();
for (Annotation declaredAnnotation : declaredAnnotations) {
System.out.println("declaredAnnotation = " + declaredAnnotation);
}
ClassAnnotation demoAnnotation = testClass.getAnnotation(ClassAnnotation.class);
if (demoAnnotation!=null){
System.out.println("demoAnnotation = " + demoAnnotation);
}
ClassAnnotation declaredDemoAnnotation = testClass.getDeclaredAnnotation(ClassAnnotation.class);
if (declaredDemoAnnotation!=null){
System.out.println("declaredDemoAnnotation = " + declaredDemoAnnotation);
}
//獲取方法上的
System.out.println(" ===========方法上的========== " );
Method info = testClass.getMethod("info");
DemoAnnotation annotation = info.getAnnotation(DemoAnnotation.class);
System.out.println("annotation = " + annotation);
DemoAnnotation declaredAnnotation = info.getDeclaredAnnotation(DemoAnnotation.class);
System.out.println("declaredAnnotation = " + declaredAnnotation);
Annotation[] infoAnnotations = info.getAnnotations();
for (Annotation infoAnnotation : infoAnnotations) {
System.out.println("infoAnnotation = " + infoAnnotation);
}
Annotation[] infoDeclaredAnnotations = info.getDeclaredAnnotations();
for (Annotation infoDeclaredAnnotation : infoDeclaredAnnotations) {
System.out.println("infoDeclaredAnnotation = " + infoDeclaredAnnotation);
}
}
}
五、Java8新增的注解
1. @Repeatable
重復(fù)注解:即同一程序元素需要使用多個一樣的注解的時候使用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableTags.class)
public @interface RepeatableTag {
String name() default "haizi";
int age() default 18;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RepeatableTags {
RepeatableTag[] value();
}
//這是傳統(tǒng)寫法
//@RepeatableTags({
// @RepeatableTag(name = "hazi",age = 18),
// @RepeatableTag(name = "luoyu",age = 17)
//})
//java8提供的寫法
@RepeatableTag(name = "haizi",age = 19)
@RepeatableTag(name = "luoyu",age = 18)
public class RepeatableTest {
}
public static void main(String[] args) {
Class<RepeatableTest> repeatableTestClass = RepeatableTest.class;
RepeatableTag[] declaredAnnotationsByType = repeatableTestClass.getDeclaredAnnotationsByType(RepeatableTag.class);
//java8的寫法
for (RepeatableTag repeatableTag : declaredAnnotationsByType) {
System.out.println("repeatableTag = " + repeatableTag);
}
//傳統(tǒng)寫法
RepeatableTags declaredAnnotation = repeatableTestClass.getDeclaredAnnotation(RepeatableTags.class);
for (RepeatableTag repeatableTag : declaredAnnotation.value()) {
System.out.println("repeatableTag = " + repeatableTag);
}
/**
* 這里其實是獲取不的 返回null,原因是兩個@RepeatableTag注解其實以及被默認組裝成
* @RepeatableTags注解,即@RepeatableTag以及被@RepeatableTags代替了
*/
RepeatableTag repeatableTag = repeatableTestClass.getDeclaredAnnotation(RepeatableTag.class);
System.out.println("repeatableTag = " + repeatableTag);
}
2. java8新增的Type Annotation
java8為ElementType枚舉增加了TYPE_PARAMETER、和、TYPE_USE兩個枚舉值
TYPE_USE:被稱為類型注解可以讓被修飾的注解放在任何用到類型的地方
創(chuàng)建對象(用new 關(guān)鍵字創(chuàng)建時)
類型轉(zhuǎn)換
使用implement實現(xiàn)接口
使用throws聲明拋出異常
......
說明:這樣做是為了讓編譯器執(zhí)行更嚴格的編譯檢查,保證代碼的健壯性
六、編譯時處理Annotation
APT(Annotation Processing Tool)是一種注解處理工具,它對源代碼文件進行檢測,并找出源文件所含的Annotation信息,然后針對不通的Annotation進行額外的處理
Java提供的javac.exe工具指定有一個-processor選項,該選項可以指定一個Annotation處理器
Annotation處理器:每個自定義的Annotation處理去都需要實現(xiàn)javax.annotation.processing包下的Processor接口,不過實現(xiàn)該接口就必須實現(xiàn)接口中的所有方法,因此通常會采用繼承AbstractProcessor的方式來實現(xiàn)Annotation處理器,一個Annotation處理器可以處理多種Annotation類型。
1. 案例
自定義APT根據(jù)源文件中的注解來生成額外的文件,下面將定義3種注解類型分別用于修飾持久化類,標(biāo)識屬性普通成員屬性(案例來自《瘋狂java講義第3版》)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Persistent {
String table();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Property {
String column();
String type();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Id {
String column();
String type();
String generator();
}
@Persistent(table = "person")
public class Person {
@Id(column = "person_id", type = "Integer", generator = "identity")
private Integer id;
@Property(column = "person_name", type = "String")
private String name;
@Property(column = "person_age", type = "Integer")
private Integer age;
public Person() {
}
public Person(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
//指定編譯最低版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"Persistent", "Id", "Property"})
public class MyProcess extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
PrintStream ps = null;
try {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Persistent.class);
for (Element element : elements) {
//獲取正在處理的類名
Name simpleName = element.getSimpleName();
Persistent persistent = element.getAnnotation(Persistent.class);
ps = new PrintStream(new FileOutputStream(simpleName + ".hbm.xml"));
ps.println("<?xml version=\"1.0\"?>");
ps.println(" \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"");
ps.println(" \"http://www.hibernate.org/dtd/ hibernate-mapping-3.0.dtd\">");
ps.println("<hibernate-mapping>");
ps.println(" <class name=\"" + element);
ps.println("\" table=\"" + persistent.table() + "\">");
List<? extends Element> list = element.getEnclosedElements();
for (Element element1 : list) {
if (element1.getKind() == ElementKind.FIELD) {
Id id = element1.getAnnotation(Id.class);
if (id != null) {
ps.println(" <id name=\""
+ element1.getSimpleName()
+ "\" column=\"" + id.column()
+ "\" type=\"" + id.type()
+ "\">");
}
}
Property property = element1.getAnnotation(Property.class);
if (property != null) {
ps.println(" <property name=\""
+ element1.getSimpleName()
+ "\" column=\"" + property.column()
+ "\" type=\"" + property.type()
+ "\">");
}
}
}
ps.println(" </class>");
ps.println("</hibernate-mapping>");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return true ;
}
}


