目錄結構:java
用一個詞就能夠描述註解,那就是元數據,即一種描述數據的數據。因此,能夠說註解就是源代碼的元數據。好比,下面這段代碼:程序員
@Override public String toString() { return "This is String Representation of current object."; }
上面的代碼中,我重寫了toString()方法並使用了@Override註解。可是,即便我不使用@Override註解標記代碼,程序也可以正常執行。那麼,該註解表示什麼?這麼寫有什麼好處嗎?事實上,@Override告訴編譯器這個方法是一個重寫方法(描述方法的元數據),若是父類中不存在該方法,編譯器便會報錯,提示該方法沒有重寫父類中的方法。若是我不當心拼寫錯誤,例如將toString()寫成了toStrring(){double r},並且我也沒有使用@Override註解,那程序依然能編譯運行。但運行結果會和我指望的大不相同。如今咱們瞭解了什麼是註解,而且使用註解有助於閱讀程序。數組
Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。安全
使用Annotation以前(甚至在使用以後),XML被普遍的應用於描述元數據。不知什麼時候開始一些應用開發人員和架構師發現XML的維護愈來愈糟糕了。他們但願使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是鬆耦合的(在某些狀況下甚至是徹底分離的)代碼描述。架構
假如你想爲應用設置不少的常量或參數,這種狀況下,XML是一個很好的選擇,由於它不會同特定的代碼相連。若是你想把某個方法聲明爲服務,那麼使用Annotation會更好一些,由於這種狀況下須要註解和方法緊密耦合起來,開發人員也必須認識到這點。app
另外一個很重要的因素是Annotation定義了一種標準的描述元數據的方式。在這以前,開發人員一般使用他們本身的方式定義元數據。例如,使用標記interfaces,註釋,transient關鍵字等等。每一個程序員按照本身的方式定義元數據,而不像Annotation這種標準的方式。ide
編寫Annotation很是簡單,能夠將Annotation的定義同接口的定義進行比較。咱們來看兩個例子:一個是標準的註解@Override,另外一個是用戶自定義註解@Todo。函數
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
對於@Override註釋你可能有些疑問,它什麼都沒作,那它是如何檢查在父類中有一個同名的函數呢。固然,不要驚訝,我是逗你玩的。@Override註解的定義不只僅只有這麼一點代碼。這部份內容很重要,我不得再也不次重複:Annotations僅僅是元數據,和業務邏輯無關。理解起來有點困難,但就是這樣。若是Annotations不包含業務邏輯,那麼必須有人來實現這些邏輯。元數據的用戶來作這個事情。Annotations僅僅提供它定義的屬性(類/方法/包/域)的信息。Annotations的用戶(一樣是一些代碼)來讀取這些信息並實現必要的邏輯。
當咱們使用Java的標註Annotations(例如@Override)時,JVM就是一個用戶,它在字節碼層面工做。到這裏,應用開發人員還不能控制也不能使用自定義的註解。所以,咱們講解一下如何編寫自定義的Annotations。
咱們來逐個講述編寫自定義Annotations的要點。上面的例子中,你看到一些註解應用在註解上。工具
J2SE5.0版本在 java.lang.annotation提供了四種元註解,專門註解其餘的註解:
@Documented –註解是否將包含在JavaDoc中
@Retention –何時使用該註解
@Target? –註解用於什麼地方
@Inherited – 是否容許子類繼承該註解
@Documented–一個簡單的Annotations標記註解,表示是否將註解信息添加在java文檔中。
@Retention– 定義該註解的生命週期。
RetentionPolicy.SOURCE – 在編譯階段丟棄。這些註解在編譯結束以後就再也不有任何意義,因此它們不會寫入字節碼。@Override, @SuppressWarnings都屬於這類註解。
RetentionPolicy.CLASS – 在類加載的時候丟棄。在字節碼文件的處理中有用。註解默認使用這種方式。
RetentionPolicy.RUNTIME– 始終不會丟棄,運行期也保留該註解,所以可使用反射機制讀取該註解的信息。咱們自定義的註解一般使用這種方式。
@Target – 表示該註解用於什麼地方。若是不明確指出,該註解能夠放在任何地方。如下是一些可用的參數。須要說明的是:屬性的註解是兼容的,若是你想給7個屬性都添加註解,僅僅排除一個屬性,那麼你須要在定義target包含全部的屬性。
ElementType.TYPE:用於描述類、接口或enum聲明
ElementType.FIELD:用於描述實例變量
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE 另外一個註釋
ElementType.PACKAGE 用於記錄java文件的package信息
@Inherited – 定義該註釋和子類的關係
那麼,註解的內部究竟是如何定義的呢?Annotations只支持基本類型、String及枚舉類型。註釋中全部的屬性被定義成方法,並容許提供默認值。測試
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Todo { public enum Priority {LOW, MEDIUM, HIGH} public enum Status {STARTED, NOT_STARTED} String author() default "Yash"; Priority priority() default Priority.LOW; Status status() default Status.NOT_STARTED; }
下面的例子演示瞭如何使用上面的註解。
@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED) public void incompleteMethod1() { //Some business logic is written //But it’s not complete yet }
若是註解中只有一個屬性,能夠直接命名爲「value」,使用時無需再標明屬性名。
@interface Author{ String value(); } @Author("Yashwant") public void someMethod() { }
在Java8之前,同一個程序元素前只能使用一個相同類型的Annotation;若是須要在同一個元素前使用多個類型相同的Annotation,則必須使用Annotation「容器」。
下面先介紹這種「容器」,
首先定義個MyTag註解:
//指定註解保留到運行時 @Retention(RetentionPolicy.RUNTIME) //指定註解能夠修飾類、接口、枚舉 @Target(ElementType.TYPE) @interface MyTag { String name() default "測試"; int age() default 20; }
而後再定義MyTag註解的容器註解:
//指定註解保留到運行時 @Retention(RetentionPolicy.RUNTIME) //指定註解能夠修飾類、接口、枚舉 @Target(ElementType.TYPE) @interface MyTags { MyTag[] value(); }
而後就能夠按照以下的方式來使用註解了
@MyTags({ @MyTag(name="測試1",age=21), @MyTag(name="測試2",age=22) }) public class Test { public static void main(String[] args) { //經過反射解析註解 Class testClass= Test.class; //得到MyTags註解 MyTags myTagsAnnotation= (MyTags) testClass.getAnnotation(MyTags.class); //得到添加到裏面的MyTag註解 MyTag[] myTags=myTagsAnnotation.value(); for(MyTag myTag : myTags) { System.out.println(String.format("name:%1$s,age:%2$d",myTag.name(),myTag.age())); } } }
打印:
name:測試1,age:21
name:測試2,age:22
java8爲上面這種繁瑣的語法提供了糖語法,在java8中新增長了@Repeatable元註解,只須要在MyTag註解上添加上元註解@Repeatable(MyTags.class)。
觀察能夠發現,@Repeatable依然須要依賴容器註解,因此依然能夠按照以下的方式來使用:
@MyTags({ @MyTag(name="測試1",age=21), @MyTag(name="測試2",age=22) })
又由於MyTag是重複註解,因此還能夠像以下這樣使用:
@MyTag(name="測試1",age=21)
@MyTag(name="測試2",age=22)
這裏須要注意,重複註解只是一種簡便寫法,多個重複註解其實會被做爲「容器」註解的value成員變量的數組元素。例如上面重複的MyTag註解會被做爲@MyTags註解的value成員變量的數組元素處理。
如今咱們已經知道了能夠經過使用@Retention註解來指定註解的生存週期,註解的生存週期有三種,分別爲:RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME,這三個值分別表示註解的生存週期爲源代碼,字節碼,運行時中。
接下來介紹註解在不一樣階段中的處理:
其實在上面的案例中,已經展現瞭如何使用反射獲取註解的數據。若是要在程序運行時處理註解,那麼必須將註解的聲明週期聲明爲: @Retention(RetentionPolicy.RUNTIME) 。
因爲註解自己是不包含任何業務邏輯的,在運行時中,咱們就能夠經過反射來實現具體的邏輯,
先定義一個Debug註解:
//指定註解保留到運行時 @Retention(RetentionPolicy.RUNTIME) //指定該註解只能用於方法 @Target(ElementType.METHOD) @interface Debug { boolean value() default false; }
接下來將該註解和具體的業務邏輯關聯起來:
public class DebugTest { public static void main(String[] args) { Class debugTestClass = DebugTest.class; //得到全部的方法 Method[] methods = debugTestClass.getMethods(); for (Method method : methods) { method.setAccessible(true);//禁用安全機制 if (method.isAnnotationPresent(Debug.class)) {//檢查是否使用了Debug註解 Debug debug = method.getAnnotation(Debug.class);//得到註解實例 String name = method.getName();//得到方法名稱 if (debug.value()) { System.out.println("method:" + name + " should debug"); } else { System.out.println("method:" + name + " should't debug"); } } } } @Debug(false) public void testMethod1() { } @Debug(true) public void testMethod2() { } @Debug(true) public void testMethod3() { } @Debug(false) public void testMethod4() { } @Debug(true) public void testMethod5() { } }
如果編譯時須要處理的註解,那麼能夠把註解的聲明週期聲明爲: @Retention(RetentionPolicy.SOURCE) 。
在這裏須要先介紹一下APT,API(Annotation Processing Tool)是一種註解處理工具,他對源代碼進行檢測,並找出源代碼所包含的Annotation信息,而後針對Annotation信息進行額外的處理。使用APT工具處理Annotation時能夠根據源文件中的Annotation生成額外的源文件和其餘的文件(文件的具體內容由Annotation處理器的編寫者決定),APT還會將編譯生成的源代碼文件和原來的源文件一塊兒生成Class文件。
使用APT的主要目的是爲了簡化開發者的工做量,由於APT能夠在編譯程序源代碼的同時生成一些附屬文件(好比源文件、類文件、程序發佈描述文件等),這些附屬文件的內容也都與源代碼相關。換句話說,使用APT能夠代替傳統的對代碼信息和附屬文件的維護工具。
Java提供的javac.exe工具備一個-processor選項,該選項可指定一個Annotation處理器,若是在編譯java源文件時指定了該Annotation處理器,那麼這個Annotation處理器將會在編譯時提取並處理Java源文件中的Annotaion.
每個Annoataion處理器都須要實現javax.annotataion.processor包下的Processor接口,不過實現該接口必須實現該接口下的全部的方法,所以一般會採用繼承AbstractProcessor的方式來實現Annotation的處理器。一個Annotation處理器能夠處理一個或多個Annotaion註解。
在Hibernate中,若是使用非註解的方式,那麼每寫一個Java Bean類文件,還必須額外地維護一個Hibernate映射文件(名爲*.hbm.xml的文件),下面將使用APT來簡化這步操做。
爲了示範使用APT根據源文件中的註解來生成額外的文件,下面定義3種註解。
標識持久化類的@Persistent 註解:
//指定該註解能夠修飾類,接口,枚舉 @Target(ElementType.TYPE) //指定該註解保留到編譯時 @Retention(RetentionPolicy.SOURCE) //指定該註解能夠被顯示在文檔中(經過javadoc生成文檔,即可以在被該註解修飾的元素上看到該註解信息) @Documented public @interface Persistent { String table(); }
標識屬性的@Id 註解:
//指定該註解只能修飾字段 @Target(ElementType.FIELD) //指定該註解保留到編譯時 @Retention(RetentionPolicy.SOURCE) //指定該註解能夠被顯示在文檔中(經過javadoc生成文檔,即可以在被該註解修飾的元素上看到該註解信息) @Documented public @interface Id { String column(); String type(); String generator(); }
標識屬性的@Property 註解
//指定該註解只能修飾字段 @Target(ElementType.FIELD) //指定該註解保留到編譯時 @Retention(RetentionPolicy.SOURCE) //指定該註解能夠被顯示在文檔中(經過javadoc生成文檔,即可以在被該註解修飾的元素上看到該註解信息) @Documented public @interface Property { String column(); String type(); }
在有了三個Annotation後,咱們定義一個簡單的Java Bean類Person.java.
@Persistent(table="personInfo") public class Person { @Id(column="person_id",type="integer",generator="identity") private int id; @Property(column="person_name",type="string") private String name; @Property(column="person_age",type="integer") private int age; public Person(){} public Person(int id,String name,int age) { this.id=id; this.name=name; this.age=age; } //全部屬性的setter和getter..... }
接下來寫一個API工具,該API工具是根據java類中的註解來生成一個Hibernate 映射文件。
import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; //指定該註解支持java平臺的最新版本爲6.0 @SupportedSourceVersion(SourceVersion.RELEASE_6) //指定能夠處理Persistent,id,Property註解 @SupportedAnnotationTypes({"Persistent","Id","Property"}) public class HibernateAnnotationProcessor extends AbstractProcessor{ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //定義文件輸出流,用於生成額外的文件 PrintStream ps=null; try{ for(Element t:roundEnv.getElementsAnnotatedWith(Persistent.class)){ //獲取正在處理的類名稱 Name className=t.getSimpleName(); //得到類定義前的@Persistent Annotation Persistent per= t.getAnnotation(Persistent.class); //建立文件輸出流 ps=new PrintStream(new FileOutputStream(new File(className+".hbm.xml"))); //執行輸出 ps.println("<?xml version=\"1.0\"?>"); ps.println("<!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\""); ps.println("\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">"); ps.println("<hibernate-mapping>"); ps.println("<class name=\""+className+"\" table=\""+per.table()+"\" >"); for(Element f:t.getEnclosedElements()) { //只處理成員變量上的Annotation if(f.getKind()==ElementKind.FIELD) { //獲取成員變量定義前的@Id Annotation Id id=f.getAnnotation(Id.class); //但@id註解存在時,輸出<id ../>元素 if(id!=null) { ps.println("<id name=\""+f.getSimpleName()+"\" "+ "column=\""+id.column()+"\" "+ "type=\""+id.type()+"\">"); ps.println("<generator class=\""+id.generator()+"\" />"); ps.println("</id>"); continue; } //獲取成員變量前的@Property Annotation Property p=f.getAnnotation(Property.class); if(p!=null) { ps.println("<property name=\""+f.getSimpleName()+"\" "+ "column=\""+p.column()+"\" "+ "type=\""+p.type()+"\" />"); continue; } } } ps.println("</class>"); ps.println("</hibernate-mapping>"); } }catch(Exception e) { e.printStackTrace(); }finally{ if(ps!=null) ps.close(); } return true; } }
在編譯完HibernateAnnotationProcessor.java後執行以下的命令:
javac -processor HibernateAnnotationProcessor Person.java
就能夠看到在該路徑下多了一個Person.cfg.xml文件
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="Person" table="personInfo" > <id name="id" column="person_id" type="integer"> <generator class="identity" /> </id> <property name="name" column="person_name" type="string" /> <property name="age" column="person_age" type="integer" /> </class> </hibernate-mapping>