編者的話:註解是java的一個主要特性且每一個java開發者都應該知道如何使用它。html
咱們已經在Java Code Geeks提供了豐富的教程, 如Creating Your Own Java Annotations, Java Annotations Tutorial with Custom Annotation 和 Java Annotations: Explored & Explained.java
咱們也有些文章是關於註解在不一樣類庫中的應用,包括 Make your Spring Security @Secured annotations more DRY和 Java Annotations & A Real World Spring Example.程序員
如今,是時候彙總這些和註解相關的信息到一篇文章了,祝你們閱讀愉快。spring
在這篇文章中咱們將闡述什麼是Java註解,它們如何工做,怎麼使用它們。數據庫
咱們將揭開Java註解的面紗,包括內建註解或稱元註解,還將討論Java8中與之相關的的新特性。api
最後,咱們將實現自定義的註解,編寫一個使用註解的處理程序(消費器),它經過java反射使用註解。數組
咱們還會列出一些基於註解,知名且被普遍應用的第三方類庫如:Junit,JAXB,Spring,Hibernate。安全
在文章的最後,會有一個壓縮文件包含了文章中的全部示例,實現這些例子使用的軟件版本以下所示:oracle
註解早在J2SE1.5就被引入到Java中,主要提供一種機制,這種機制容許程序員在編寫代碼的同時能夠直接編寫元數據。框架
在引入註解以前,程序員們描述其代碼的形式還沒有標準化,每一個人的作法各異:transient關鍵字、註釋、接口等。這顯然不是一種優雅的方式,隨之而來的一種嶄新的記錄元數據的形式——註解被引入到Java中。
其它因素也促成了這個決定:當時不一樣類型的應用程序使用XML做爲標準的代碼配置機制,這其實並非最佳方式,由於代碼和XML的解耦以及將來對這 種解耦應用的維護並不低廉。另外,因爲非保留字的使用,例如「@deprecated」自從Java1.4便開始在Java文檔中使用。我很是肯定這是一 個如今在註解中使用「@」緣由。
包含註解的設計和開發的Java規範主要有如下兩篇:
解釋何爲註解的最佳方式就是元數據這個詞:描述數據自身的數據。註解就是代碼的元數據,他們包含了代碼自身的信息。
註解能夠被用在包,類,方法,變量,參數上。自Java8起,有一種註解幾乎能夠被放在代碼的任何位置,叫作類型註解。咱們將會在後面談到具體用法。
被註解的代碼並不會直接被註解影響。這隻會向第三系統提供關於本身的信息以用於不一樣的需求。
註解會被編譯至class文件中,並且會在運行時被處理程序提取出來用於業務邏輯。固然,建立在運行時不可用的註解也是可能的,甚至能夠建立只在源文件中可用,在編譯時不可用的註解。
理解註解的目的以及如何使用它都會帶來困難,由於註解自己並不包含任何功能邏輯,它們也不會影響本身註解的代碼,那麼,它們到底爲何而存在呢?
這個問題的解釋就是我所稱的註解消費器。它們是利用被註解代碼並根據註解信息產生不一樣行爲的系統或者應用程序。
例如,在Java自帶的內建註解(元註解)中,消費器是執行被註解代碼的JVM。還有其餘稍後談到的其餘例子,例如JUnit,消費器是讀取,分析被註解代碼的JUnit處理程序,它還能夠決定測試單元和方法執行順序。咱們會在JUnit章節更深刻。
消費器使用Java中的反射機制來讀取和分析被註解的源代碼。使用的主要的包有:java.lang, java.lang.reflect。咱們將會在本篇指南中介紹如何用反射從頭開始建立一個自定義的消費器。
聲明一個註解須要使用「@」做爲前綴,這便向編譯器說明,該元素爲註解。例如:
@Annotation public void annotatedMehod() { ... }
上述的註解名稱爲Annotation,它正在註解annotatedMethod方法。編譯器會處理它。註解能夠以鍵值對的形式持有有不少元素,即註解的屬性。
@Annotation( info = "I am an annotation", counter = "55" ) public void annotatedMehod() { ... }
若是註解只包含一個元素(或者只須要指定一個元素的值,其它則使用默認值),能夠像這樣聲明:
@Annotation("I am an annotation") public void annotatedMehod() { ... }
就像咱們看到的同樣,若是沒有元素須要被指定,則不須要括號。多個註解可使用在同一代碼上,例如類:
@ Annotation (info = "U a u O") @ Annotation2 class AnnotatedClass { ... }
一些java自己提供的開箱即用的註解,咱們稱之爲內建註解。也能夠定義你本身的註解,稱之爲子定義註解。咱們會在下一章討論。
註解基本上能夠在Java程序的每個元素上使用:類,域,方法,包,變量,等等。
自Java8,誕生了經過類型註解的理念。在此以前,註解是限於在前面討論的元素的聲明上使用。今後,不管是類型仍是聲明均可以使用註解,就像:
@MyAnnotation String str = "danibuiza";
咱們將會在Java8關聯章節看到這種機制的更多細節。
註解能夠知足許多要求,最廣泛的是:
在這篇手冊中咱們將展示幾種註解可能的用法,包括流行的Java類庫是如何使用它們的。
Java語言自帶了一系列的註解。在本章中咱們將闡述最重要的一部分。這個清單隻涉及了Java語言最核心的包,未包含標準JRE中全部包和庫如JAXB或Servlet規範。
如下討論到的註解中有一些被稱之爲Meta註解,它們的目的註解其餘註解,而且包含關於其它註解的信息。
咱們會在稍後展開幾個例子。
@SuppressWarnings( "unused") private String myNotUsedMethod(){ ... }
更多信息請參考:http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html
Java8帶來了一些優點,一樣註解框架的能力也獲得了提高。在本章咱們將會闡述,並就java8帶來的3個註解作專題說明和舉例:
@Repeatable註解,關於類型註解的聲明,函數式接口註解@FunctionalInterface(與Lambdas結合使用)。
看一個使用的例子。首先咱們創造一個能容納重複的註解的容器:
/** * Container for the {@link CanBeRepeated} Annotation containing a list of values */ @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) public @interface RepeatedValues { CanBeRepeated[] value(); }
接着,建立註解自己,而後標記@Repeatable
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) @Repeatable( RepeatedValues.class ) public @interface CanBeRepeated { String value(); }
最後,咱們能夠這樣重複地使用:
@CanBeRepeated( "the color is green" ) @CanBeRepeated( "the color is red" ) @CanBeRepeated( "the color is blue" ) public class RepeatableAnnotated { }
若是咱們嘗試去掉@Repeatable
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) public @interface CannotBeRepeated { String value(); } @CannotBeRepeated( "info" ) /* * if we try repeat the annotation we will get an error: Duplicate annotation of non-repeatable type * * @CannotBeRepeated. Only annotation types marked * * @Repeatable can be used multiple times at one target. */ // @CannotBeRepeated( "more info" ) public class RepeatableAnnotatedWrong { }
咱們會獲得編譯器的錯誤信息:
Duplicate annotation of non-repeatable type
@SuppressWarnings( "unused" ) public static void main( String[] args ) { // type def @TypeAnnotated String cannotBeEmpty = null; // type List<@TypeAnnotated String> myList = new ArrayList<String>(); // values String myString = new @TypeAnnotated String( "this is annotated in java 8" ); } // in method params public void methodAnnotated( @TypeAnnotated int parameter ) { System.out.println( "do nothing" ); }
// implementing its methods @SuppressWarnings( "unused" ) MyCustomInterface myFuncInterface = new MyCustomInterface() { @Override public int doSomething( int param ) { return param * 10; } }; // using lambdas @SuppressWarnings( "unused" ) MyCustomInterface myFuncInterfaceLambdas = ( x ) -> ( x * 10 ); } @FunctionalInterface interface MyCustomInterface { /* * more abstract methods will cause the interface not to be a valid functional interface and * the compiler will thrown an error:Invalid '@FunctionalInterface' annotation; * FunctionalInterfaceAnnotation.MyCustomInterface is not a functional interface */ // boolean isFunctionalInterface(); int doSomething( int param ); }
@Documented @Retention(value=RUNTIME) @Target(value=TYPE) public @interface FunctionalInterface
正如咱們以前屢次說起的,能夠定義和實現自定義註解。本章咱們即將探討。
首先,定義一個註解:
public @interface CustomAnnotationClass
這樣建立了一個新的註解類型名爲 CustomAnnotationClass。關鍵字:@interface說明這是一個自定義註解的定義。
以後,你須要爲此註解定義一對強制性的屬性,保留策略和目標。還有一些其餘屬性能夠定義,不過這兩個是最基本和重要的。它們在第8章,描述註解的註解時討論過,它們一樣也是Java內建的註解。
因此,咱們爲自定義的註解設置屬性:
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE ) public @interface CustomAnnotationClass implements CustomAnnotationMetho
在保留策略中 RUNTIME 告訴編譯器這個註解應該被被JVM保留,而且能經過反射在運行時分析。經過 TYPE 咱們又設置該註解能夠被使用到任何類的元素上。
以後,咱們定義兩個註解的成員:
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE ) public @interface CustomAnnotationClass { public String author() default "danibuiza"; public String date(); }
以上咱們僅定義了默認值爲「danibuiza」的 author 屬性和沒有默認值的date屬性。咱們應強調全部的方法聲明都不能有參數和throw子句。這個返回值的類型被限制爲以前提過的字符串,類,枚舉,註解和存儲這些類型的數組。
如今咱們能夠像這樣使用剛建立的自定義註解:
@CustomAnnotationClass( date = "2014-05-05" ) public class AnnotatedClass { ... }
在另外一種相似的用法中咱們能夠建立一種註解方法的註解,使用Target METHOD:
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.METHOD ) public @interface CustomAnnotationMethod { public String author() default "danibuiza"; public String date(); public String description(); }
這種註解可使用在方法聲明上:
@CustomAnnotationMethod( date = "2014-06-05", description = "annotated method" ) public String annotatedMethod() { return "nothing niente"; } @CustomAnnotationMethod( author = "friend of mine", date = "2014-06-05", description = "annotated method" ) public String annotatedMethodFromAFriend() { return "nothing niente"; }
有不少其它屬性能夠用在自定義註解上,可是 目標 (Target)和 保留策略(Retention Policy)是最重要的兩個。
Java反射API包含了許多方法來在運行時從類,方法或者其它元素獲取註解。接口AnnotatedElement包含了大部分重要的方法,以下:
class 經過java.lang.Class被實現,java.lang.reflect.Method 和 java.lang.reflect.Field,因此能夠基本上被和任何Java元素使用。
如今,咱們將看一個怎麼讀取註解的例子:
咱們寫一個程序,從一個類和它的方法中讀取全部的存在的註解:
public static void main( String[] args ) throws Exception { Class<AnnotatedClass> object = AnnotatedClass.class; // Retrieve all annotations from the class Annotation[] annotations = object.getAnnotations(); for( Annotation annotation : annotations ) { System.out.println( annotation ); } // Checks if an annotation is present if( object.isAnnotationPresent( CustomAnnotationClass.class ) ) { // Gets the desired annotation Annotation annotation = object.getAnnotation( CustomAnnotationClass.class ); System.out.println( annotation ); } // the same for all methods of the class for( Method method : object.getDeclaredMethods() ) { if( method.isAnnotationPresent( CustomAnnotationMethod.class ) ) { Annotation annotation = method.getAnnotation( CustomAnnotationMethod.class ); System.out.println( annotation ); } } }
輸出以下:
@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=friend of mine, date=2014-06-05, description=annotated method) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=danibuiza, date=2014-06-05, description=annotated method)
在這個程序中,咱們能夠看到 getAnnotations()方法來獲取全部某個對象(方法,類)上的全部註解的用法。展現了怎樣使用isAnnotationPresent()方法和getAnnotation()方法檢查是否存在特定的註解,和如何獲取它。
註解在Java中可使用繼承。這種繼承和普通的面向對象繼承幾乎沒有共同點。
若是一個註解在Java中被標識成繼承,使用了保留註解@Inherited,說明它註解的這個類將自動地把這個註解傳遞到全部子類中而不用在子類 中聲明。一般,一個類繼承了父類,並不繼承父類的註解。這徹底和使用註解的目的一致的:提供關於被註解的代碼的信息而不修改它們的行爲。
咱們經過一個例子更清楚地說明。首先,咱們定義一個自動繼承的自定義註解。
@Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface InheritedAnnotation { }
有一個父類名爲:AnnotatedSuperClass,已經被自定義的註解給註解上了:
@InheritedAnnotation public class AnnotatedSuperClass { public void oneMethod() { } }
一個子類繼承父類:
@InheritedAnnotation public class AnnotatedSuperClass { public void oneMethod() { } }
子類 AnnotatedSubClass 展現了自動繼承的註解 @InheritedAnnotation。咱們看到下面的代碼經過 isAnnotationPresent() 方法測試出了當前註解。
pre>System.out.println( "is true: " + AnnotatedSuperClass.class.isAnnotationPresent( InheritedAnnotation.class ) ); System.out.println( "is true: " + AnnotatedSubClass.class.isAnnotationPresent( InheritedAnnotation.class ) );</pre> <pre>
輸出以下:
is true: true is true: true
咱們能夠看到子類雖然並無聲明註解,但仍是被自動地註解上了。
若是咱們嘗試註解在一個接口中:
@InheritedAnnotation public interface AnnotatedInterface { public void oneMethod(); }
一個實現了該接口的類:
public class AnnotatedImplementedClass implements AnnotatedInterface { @Override public void oneMethod() { } }
通過 isAnnotationPresent() 方法測試:
System.out.println( "is true: " + AnnotatedInterface.class.isAnnotationPresent( InheritedAnnotation.class ) ); System.out.println( "is true: " + AnnotatedImplementedClass.class.isAnnotationPresent( InheritedAnnotation.class ) );
結果以下:
is true: true is true: false
這個結果說明繼承註解和接口在一塊兒使用時,接口中的註解在實現類中:僅僅被忽略。實現類並不繼承接口的註解;接口繼承僅僅適用於類繼承。正如 AnnotatedSubClass。
@Inheriated註解僅在存在繼承關係的類上產生效果,在接口和實現類上並不工做。這條一樣也適用在方法,變量,包等等。只有類才和這個註解連用。
一條關於@Inheriated註解的很好的解釋在Javadoc中:http://docs.oracle.com/javase/7/docs/api/java/lang/annotation/Inherited.html.
註解不能繼承註解,若是你嘗試這麼作了,就會獲得編譯器拋出的錯誤:
Annotation type declaration cannot have explicit superinterfaces
在這一章咱們將展現知名類庫是如何利用註解的。一些類庫如:JAXB, Spring Framework, Findbugs, Log4j, Hibernate, Junit。它們使用註解來完成代碼質量分析,單元測試,XML解析,依賴注入和許多其它的工做。
在這篇手冊中咱們將討論如下類庫的部份內容:
這個框架用於完成Java中的單元測試。自JUnit4開始,註解被普遍應用,成爲Junit的設計的主幹之一。
基本上,JUnit處理程序經過反射讀取類和測試套件,按照在方法上,類上的註解順序地執行它們。固然還有一些用來修改測試執行的註解。其它註解都用來執行測試,阻止執行,改變執行順序等等。
用到的註解至關多,可是咱們將會看到最重要的幾個:
@Test public void testMe() { //test assertions assertEquals(1,1); }
@Before public void setUp() { // initializing variables count = 0; init(); }
@After public void destroy() { // closing input stream stream.close(); }
@Ignore @Test public void donotTestMe() { count = -22; System.out.println( "donotTestMe(): " + count ); }
還有一些測試套件和類庫使用了註解,例如Mockito和JMock,使用註解來建立測試對象和預期值。
Hibernate多是一個用得最普遍的對象關係映射類庫。它提供了對象模型和關係型數據庫的映射框架。使用註解做爲設計的一部分。
在本章咱們將討論一兩個由Hibernate提供的註解並解釋它的處理程序如何處理它們。
下面的代碼段使用了@Entity和@Table。這兩個是用來向消費器(Hibernate處理程序)說明被註解的類是一個實體類,以及它映射的SQL表名。實際上,這個註解僅僅是指明瞭主表,還能夠有說明字表的註解。
@Entity @Table( name = "hibernate_annotated" ) public class HibernateAnnotated
接下來的代碼段展現瞭如何向Hibernate處理程序說明被標記的元素是表的主鍵,並映射名爲「id」的列,而且主鍵是自動生成的。
@Id @GeneratedValue @Column( name = "id" ) private int id;
爲了指定標準的SQL表列名,咱們能夠寫以下註解:
@Column( name = "description" ) private String description;
這說明被標記的元素映射的是這個類所對應的表中名爲「description」的一列。
這些註解都來自 http://docs.oracle.com/javaee/6/api/javax/persistence/package-summary.html 企業級Java的包。它們幾乎涵蓋了全部 Hibernate 全部可用的註解。
Spring是個被普遍使用的Java企業級應用框架。其一項重要的特性就是在Java程序使用依賴注入。
Spring使用註解做爲XML(Spring的早期版本使用的基於XML配置)的一種替代方式。如今二者都是可行的。你可使用XML文件或者註解配置你的項目。在我看來二者都各有優點。
咱們將在下例中展現兩個可用註解:
@Component public class DependencyInjectionAnnotation { private String description; public String getDescription() { return description; } @Autowired public void setDescription( String description ) { this.description = description; } }
在次代碼片斷中咱們能夠找到兩個使用在了類和方法上的註解。
更多關於依賴注入和Spring框架的細節請參考:http://projects.spring.io/spring-framework/.
這是一個用來測量代碼質量,並提供一系列可能提升它的工具。它會根據預約義(或者自定義)的違反規則來檢查代碼。Findbugs提供一系列註解來容許開發者來改變默認行爲。
它主要使用反射讀取代碼(和包含的註解)並決定基於它們,應該採起什麼行爲。
一個列子是 edu.umd.cs.findbugs.annotations.SuppressFBWarnings 註解期待一種違反規定的鍵值並把它看成參數。這很是像 java.lang.SuppressWarnings。被用來向 Findbugs 說明當執行代碼分析的時候,忽略指定的違反規則。
這是一個例子:
@SuppressFBWarnings( "HE_EQUALS_USE_HASHCODE" ) public class FindBugsAnnotated { @Override public boolean equals( Object arg0 ) { return super.equals( arg0 ); } }
這個類已經重現了 Object 的 equals 方法,可是並無重寫 hashCode 方法。這一般會致使問題,由於 hashCode 和 equals 二者應該被同時重寫,不然在使用這個對象做爲 HashMap 的key值的時候會致使錯誤。因此,Findbugs會在違反規則報告中創造一個錯誤條目。
若是註解 @SuppressFBWarnings 設置了 HE_EQUALS_USE_HASHCODE 值之後,處理程序就不會在拋出這類型的錯誤了。
Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode() Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode()
這個類重寫了 equals 方法,可是沒有重寫 hashCode 方法,繼承自 java.lang.Object 的 hashCode 方法(返回每一個對象被JVM賦予的特定值)。所以,這個類幾乎違反了相同的對象必須 hashcode 相同的一致性。
若是你認爲這個類的實例不會插入到哈希表,推薦的作法是像這樣實現 hashCode 方法:
public int hashCode() { assert false : "hashCode not designed"; return 42; // any arbitrary constant will do } Rank: Troubling (14), confidence: High Pattern: HE_EQUALS_USE_HASHCODE Type: HE, Category: BAD_PRACTICE (Bad practice)
這個錯誤包含了該問題的一種解釋而且提示如何處理它。在這種狀況下,解決方案應該是實現 hashCode 方法。
想知道FindBugs在 SuppressFBWarnings 註解全部可用的違反規則設置,請參考:http://findbugs.sourceforge.net/bugDescriptions.html.
JAXB是一個用來相互轉換和映射XML文件與Java對象的類庫。實際上,這個類庫與標準JRE一塊兒提供,不須要任何額外的下載和配置。能夠直接經過引入 java.xml.bind.annotation 包下的類直接使用。
JAXB使用註解來告知處理程序(或者是JVM)XML文件與代碼的相互轉化。例如,註解能夠用來在代碼上標記XML節點,XMl屬性,值等等。咱們將看到一個例子:
首先,咱們聲明一個類說明它應該是XML文件中的一個節點:
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlType( propOrder = { "brand", "model", "year", "km" } ) @XmlRootElement( name = "Car" ) class Car ...
使用註解@XmlType,@XmlRoootElement。它們用來告知 JAXB 處理程序 Car 這個類在轉換後,將會轉換成爲XML中的一個節點。這個@XmlType說明了屬性在XML中的順序。JAXB將會基於這些註解執行合適的操做。
除了分離的 getter 和 setter 屬性,不再須要向這個類中添加其它東西來完成轉換。如今咱們須要一個消費器程序來執行轉換成XML:
Car car = new Car(); car.setBrand( "Mercedes" ); car.setModel( "SLK" ); car.setYear( 2011 ); car.setKm( 15000 ); Car carVW = new Car(); carVW.setBrand( "VW" ); carVW.setModel( "Touran" ); carVW.setYear( 2005 ); carVW.setKm( 150000 ); /* init jaxb marshaler */ JAXBContext jaxbContext = JAXBContext.newInstance( Car.class ); Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); /* set this flag to true to format the output */ jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true ); /* marshaling of java objects in xml (output to standard output) */ jaxbMarshaller.marshal( car, System.out ); jaxbMarshaller.marshal( carVW, System.out );
程序產生的輸入以下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 02 <Car> <brand>Mercedes</brand> <model>SLK</model> <year>2011</year> <km>15000</km> </Car> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Car> <brand>VW</brand> <model>Touran</model> <year>2005</year> <km>150000</km> </Car>
更多關於JAXB XML和Java 的轉換信息請參考:https://jaxb.java.net/
在這篇文檔中,咱們解釋了Java註解是Java1.5開始一個很是重要的特性。基本上,註解都是做爲包含代碼信息的元數據而被標記到代碼中。它們不會改變或者影響代碼的任何意義,而是被第三方稱爲消費器的程序經過反射的方式使用。
咱們列出了Java默認的內建註解,一些稱爲元註解例如:@Target或者 @Retention,又有@Override,@SuppressWarnings,還有一些Java8相關的註解,比 如:@Repeatable,@FunctionalInterface和類型註解。咱們還展示了一兩個結合使用反射的例子,並描述了一些使用註解的類庫 例如Spring, Junit,Hibernate。
註解是Java中一種分析元數據的強大機制,能夠在不一樣的程序中擔任不一樣的做用,例如校驗,依賴注入,單元測試。
這是一個java註解的教程。
你能夠從這裏下載本教程全部代碼: customAnnotations
這是一些很是有用的關於Java註解的資料:
官方Java註解地址:http://docs.oracle.com/javase/tutorial/java/annotations/
維基百科中關於Java註解的解釋:http://en.wikipedia.org/wiki/Java_annotation
Java規範請求250:http://en.wikipedia.org/wiki/JSR_250
Oracle 註解白皮書:http://www.oracle.com/technetwork/articles/hunter-meta-096020.html