歡迎您光臨本站 註冊首頁

Java批註的發明起因及代碼應用實例

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

  批註能夠消除樣板代碼,讓源代碼的可讀性更高,並能提供級別更高的錯誤檢查。從EJB3到JUnit4,哪裡都在使用它。本文就將告訴你如何使用它。

  Java 5向Java引入了批註(Annotations),它的使用迅速成為現代Java開發中不可缺少的一部分。在正式開始介紹它之前,看看為什麼要發明批註,這是非常值得的。

  自從Java誕生之日起,人們就一直在解決它初期忽視了的一些問題:缺少元數據;缺乏將Java以外的代碼嵌入到Java源代碼文件里的能力等。當Java面市的時候,針對這些問題而推出的JavaDoc終於讓它變完整了。JavaDoc使用了在代碼里專門標記註釋的概念,從而讓它能夠提取出額外的信息,說具體點就是文檔,並將它轉換成為我們熟悉的JavaDoc文檔。這是一項簡單的技術,人人都可以使用。首先會有Doclet,目的是讓人們擴展文檔的輸出。然後是Xdoclet,它像使用標記一樣使用JavaDoc來生成代碼,從而將整個過程變得輕而易舉。這部分是對J2EE的複雜性的回應。J2EE原來依靠很多樣板代碼(boilerplate code)把對象捆綁到J2EE框架里。但是這些方案都有一些問題。首先,註釋里的標記從來都不會進入最終的源代碼,所以除非你生成代碼來反映這些標記,否則你無法在運行期間查找到它。其次,它會把整個預處理層加到(在理想情況下應該是)一個簡單編譯過程里。最後,基於註釋的標記在編譯期間並不是很容易檢查,也無法輕易被很多IDE檢查;如果你把註釋標記拼寫錯了,編譯器是不會注意到的,編譯器只會關注那些它知道確切名字的標記。

  要解決這所有的問題,Java新增了批註。批註是用於Java語言的本機元數據標記。它們的輸入嚴格與Java語言的其他部分類似,可以通過反映被發現,更容易地讓IDE和編譯器的編寫者管理。現在就讓我們看一些被批註的代碼吧;我們先從BaseExample開始,它是一個簡單類,只帶有一個方法——myMethod:

  public class BaseExample {

  public BaseExample() {}

  public void myMethod() {

  System.out.println("This is the BaseExample");

  }

  }

  現在,我們想要擴展BaseExample並替代myMethod。下面就是完成這一任務的Example1代碼:

  public class Example1 extends BaseExample {

  public Example1() {}

  @Overridepublic void myMethod() {

  System.out.println("This is the Example1");

  }

  }

  這樣我們就有了第一個關於myMethod的批註——@Override。這是一系列內置的批註之一。@Override的意思是「方法必須替代其超類中的一個方法;如果做不到這一點,那麼就會有東西出錯,使得編譯器產生錯誤」。沒有@Override,代碼照樣會正常工作,但是假設有人修改BaseExample,讓myMethod帶有參數。如果你沒有使用@Override批註,代碼仍然會被編譯,隱藏了子類沒有替代超類方法的問題。如果有@Override的話,你會在編譯期間看到發生錯誤。

  你可能會認為「難道語言的擴展沒有解決這個問題,額外的關鍵字可能會嗎」,是的,它可能已經實現了這一點,但是這不僅沒有給語言帶來任何靈活性,還會導致很多源代碼兼容性的問題。批註這種方式避免了改變Java語言本身(當然除了增加了@markup),並且還能夠放在代碼的不同部分里,而不僅僅是在標記方法里。

  關於批註還有一點是,你可以創建自己的批註標記,這正是我們馬上要討論的內容。想一想下面這個問題:我們有一些簡單的Java Beans程序,它們都帶有不同的字元串欄位。我們希望能夠有一些通用窗體顯示代碼,它們能夠用其他顯示提示(比如寬度)來正確地標示這些欄位。現在我們可以編寫一個超類,它能夠提取出這個數據,比如說從一個在每個類里都帶有一些靜態支持方法的靜態數組裡,但是這也意味著要強制給代碼分層。利用批註做到這一點就要簡單得多了。現在讓我們從定義FormLabel.java里的FormLabel的批註開始:

  import java.lang.annotation.*;

  @Retention(RetentionPolicy.RUNTIME)

  @Target(ElementType.METHOD)

  public@interface FormLabel {String label();

  int width() default 40;

  }

  你應該注意到的第一件事是Java使用了它自己內置的一些批註來定批註:@Retention和@Target。@Retention用來定義通過設置RetentionPolicy的值批註能夠在構建-運行過程中存留多久。這裡我們使用了RUNTIME,這意味著我們定義的批註將會在運行期間被保留在代碼里。RetentionPolicy.SOURCE將被用於一個我們希望被編譯器使用然後拋棄的批註。RetentionPolicy.CLASS讓它們保留在生成的類文件里,但是能夠在運行期間被Java虛擬機(JVM)訪問到。

  在默認情況下,你可以在代碼里的任何地方都應用批註。@Target批註讓你能夠將它限制在代碼的特定部分里。在本文里,我們把目標瞄準了ElementType.METHOD,這意味著它只能夠與方法關聯在一起。其他ElementTypes有CONSTRUCTOR、FIELD、LOCAL_VARIABLE、PACKAGE、PARAMETER和TYPE,每個都能夠把批註限制到該種類型的Java語言元素,所以例如,設置TYPE將只允許批註為定義過的這種類型,比如:

  @OurAnnotation

  public class OurAnnotatedClass {…

  值得注意的是,@Target批註能夠接受單個ElementType或者一個ElementType數組,如果你想要將批註限制為一系列語言元素的話。
  下面一部分是批註介面的定義;這就像是一個普通的介面聲明,除了我們用@interface將其標記為一個批註。在這個介面里,我們然後定義批註的方法,就像我們希望用在與批註相關聯的信息上的抽象方法,所以我們就有了String label(),用於一個叫做label的字元串屬性。如果我們沒有方法,那麼批註就只能用於「做標記」,而@Overrides註釋就是這樣一個例子。如果你只有一個屬性,它最好被命名為「value」,因為當帶有一個未命名參數的批註在設置這個值時,它工作得最好。屬性還可以有默認值,比如「int width() de






fault 40;」就是在定義一個默認值為40的整數屬性。

  這就是批註定義。我們現在就可以在代碼里使用它了。下面一個SimpleData類就用到了它。

  public class SimpleData {

  private String firstname;

  private String lastname;

  private String postcode;

  public SimpleData() {}

  @FormLabel(label="First Name")

  public String getFirstname() { return firstname; }

  public void

  setFirstname(String firstname) {this.firstname = firstname;}

  @FormLabel(label="Last Name",width=80)

  public String getLastname() { return lastname; }

  public void setLastname(String lastname) {

  this.lastname = lastname;

  }

  @FormLabel(label="Postal code",width=10)

  public String getPostcode() { return postcode; }

  public void setPostcode(String postcode) {

  this.postcode = postcode;

  }

  }

  當然,如果我們不查找批註,那麼它們對代碼的執行就不會造成任何不同。我們所需要的是在運行期間使用批註的方式;我們通過Reflection API來達到這一目的。現在就讓我們創建一個簡單的processForm方法,它能夠在任何對象里查找批註。

  public void processForm(Object o) {

  for(Method m:o.getClass().getMethods()) {

  我們將在傳遞給方法的對象的類里定義所有的方法。現在,我們需要檢查每個方法,看看它們是否有FormLabel批註,以及是否返回一個String(為了簡單地說明問題,我們給所有的結果多返回一些代碼):

  if(m.isAnnotationPresent(FormLabel.class) &&

  m.getReturnType()==String.class) {

  現在我們可以通過使用Method的getAnnotation()方法來提取FormLabel批註:

  FormLabel formLabel=

  m.getAnnotation(FormLabel.class);

  現在我們執行方法來取得其字元串值,並通過在批註介面里定義的方法訪問批註屬性。下面我們就把它們列印出來:

  try {

  String value=(String)m.invoke(o);

  String label=formLabel.label();

  int width=formLabel.width();

  System.out.printf("%s[%d]:%s\n",label,width,value);

  } catch (IllegalArgumentException ex) {

  ex.printStackTrace();

  }

  catch (IllegalAccessException ex) {

  ex.printStackTrace();}

  catch (InvocationTargetException ex) {

  ex.printStackTrace();

  }

  }

  }

  }

  現在我們可以創建含有@FormLabel批註的新類,並把它們傳遞給processForm方法。這是在運行期間訪問你自己的批註的基礎。

  現在這個時候,我們回頭看看Java 5裡面其他關於批註的內容。首先是編譯器指令——@Deprecated和@SuppressWarnings。@Deprecated是把方法標示為被否定的增強方法;不推薦把它用在新代碼里,以防止以後刪除。用@Deprecated可以生成一個來自編譯器的相關警告。

  @SuppressWarnings會阻止編譯器在封閉代碼元素里警告你,所以你可以在類定義的開始或者對特定的方法使用@SuppressWarnings。它可以帶參數,用來指定需要取消的錯誤的類型,例如:

  @SuppressWarnings("unchecked")

  public List getList() {

  List l=new LinkedList();

  return l;

  }

  這裡我們取消了一個關於在List和List之間的「未檢查」的強制轉換。當你開始用Java編程但是沒有非一般代碼的時候,這就非常有用。在取消警告的時候,儘可能地縮小取消的範圍是值得的;在上面的例子里,我們取消了整個代碼。我們可以把它變緊湊,只隱藏一個語句的錯誤:

  public List

  getListToo() {

  @SuppressWarnings("unchecked")

  List l=new LinkedList();

  return l;

  }

  要注意的是,你需要在Java2SE 1.5.06或者以上的版本上進行這項工作;這之前的版本沒有提供對@SuppressWarning支持。

  Java 5里其他內置的批註都與對批註的支持有關——@Documented和@Inherited。它們都可以被加到批註定義里。@Documented的作用是,批註的使用應該在所有生成的JavaDoc文檔里都反映出來。正如你可能看到的,批註和JavaDoc標記是互補的。@Inherited的意思是,當另外一個類用類來擴展批註時,批註應該是可繼承的;在默認情況下,批註是不能被繼承的。

  你可能很希望在自己的開發項目里使用Java批註的方法。就像我在引言里講到的,批註已經成為現代Java框架和應用程序的重要一部分;就拿JUnit4舉個例子,Java批註已經允許JUnit的開發人員有了以更豐富的方式表示測試的方法,而不用要求測試編寫者強制使用統一的命名規則。還有Grails,這裡批註可以被用來向「類似鐵軌(rails-like)」的框架提供信息。批註的能力有很多,但是要記住,能力越大,責任也越大。批註是為了給開發人員提供標記信息,而不是用來隱藏運行配置。

  你可以在這裡下在本教程里所有示例的源代碼。

  DJ Walker-Morgan是一名諮詢開發人員,專長是Java和用戶到用戶的消息傳送和視頻會議。

[火星人 ] Java批註的發明起因及代碼應用實例已經有389次圍觀

http://coctec.com/docs/linux/show-post-189884.html