初識Java注解
所謂的元數據是指用來描述數據的數據,可能剛聽到元數據的時候你會有點陌生,其實任何一個使用過struts或者hibernate的開發人員都在不知不覺中使用元數據,更通俗一點來說元數據是指描述代碼間關系或者代碼與其它資源(例如數據庫表)之間內在聯系的數據,對Struts來說就是struts-config.xml文件,對hibernate來說就是.hbm文件。 但是現有的以xml或其它方式存在的元數據文件都有一些不便之處(如:與被描述的文件分離,不利於一致性維護)。
基於元數據的廣泛應用,JDK1.5引入了注解(Annotation)的概念來描述元數據,為我們提供了一種在代碼中添加信息的方法,使我們可以在運行時或某個時刻方便地使用這些數據(通過解析注解來使用這些數據)
注解的作用
A、編寫文檔:通過代碼里標識的元數據生成文檔,常用的有@param、@return等;。
B、代碼分析:通過代碼里標識的元數據對代碼進行分析。
(1)替換.properties和xml配置文件:注解可以作為軟件部署的一部分配置信息,提供了一種簡單和易於理解的方式。然而,當配置信息需要在運行時動態改變時,注解就不適合了。比較常見的是從spring 2.5開始的基於注解的配置,其作用就是減少配置文件;現在的框架基本都使用了這種配置來減少配置文件的數量。
(2)支持橫切關注點:注解能夠很好的處理依賴注入、服務發現管理對象、驗證和許多其他類似的事情。如果需要用到面向方面編程,而你不想另外使用一種面向方面的語言(如AspectJ),這時注解是一個可行的選擇。
C、編譯檢查:通過代碼里標識的元數據讓編譯器實現基本的編譯檢查。
Java內置的注解集
注解可以用於類、方法、變量、參數和包等程序元素,Java定義了一個內置的注解集:
(1)用於Java代碼的注解:
@Override:校驗方法是重寫方法,如果方法在父類中未找到會產生一個編譯警告。
@Deprecated:標記方法已經廢棄不用了,如果還在使用此方法會產生一個編譯警告。
@SuppressWarnings:告知編譯器抑制由注解參數指定的編譯時期警告。
(2)用於其它的注解:以下說明的元注解有一個共同的特點就是它們都只能用在Annotation的聲明上
@Retention:用來聲明注解的保留策略,即生命范圍,有CLASS、RUNTIME和SOURCE這三種,分別表示注解保存在類文件、JVM運行時和源代碼中。只有當聲明為RUNTIME的時候,才能夠在運行時刻通過反射API來獲取到注解的信息;如果注解聲明中不存在Retention注解,則保留策略默認為RetentionPolicy.CLASS。
@Target:指示注解所適用的程序元素的種類,即注解作用范圍(如方法、字段等),如果注解聲明中不存在Target,則聲明的注解可以用在任一程序元素上。
@Documented:指示某一類型的注解將通過javadoc和類似的默認工具進行文檔化。
@Inherited:只能用於Class級別的Annotation,用來說明被標記的Annotation會被該類的所有子類自動繼承
自定義注解
Java允許自定義注解,通過在類名前使用@interface來定義。包java.lang.annotation中包含所有定義自定義注解所需的元注解和接口,如接口java.lang.annotation.Annotation是所有注解繼承的接口,且是自動繼承,不需要定義時指定,類似於所有類都自動繼承Object。
示例:
package com.test; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyAnnotation { public String name(); public int value() default 0; }
解說:上例中的每一個方法實際上是聲明了一個配置參數,參數的名稱就是方法的名稱,參數的類型就是方法的返回值類型(返回值類型只能是基本類型、Class、String、enum),可以通過default來聲明參數的默認值。
細說Java注解
初步了解Java注解后,此處細說一下一些參數
(1)@Target表示注解的作用范圍,其可選的參數值在枚舉類ElemenetType中,包括:
(2)@Retention表示在什么級別保存注解信息,其可選的參數值在枚舉類型RetentionPolicy中,包括:
注解處理器
在程序中添加的注解,可以在編譯時或是運行時通過注解處理器來進行處理,注解處理器的本質是通過Java反射來處理。
應用:自定義一個注解,使用Java反射來解析注解
網上看到一個比較酷的案例,現展示如下:
注解的定義:
package com.annotation.test; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 聯系方式校驗 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Documented public @interface ContactValidator { public ContactType type(); }
聯系方式枚舉類:
package com.annotation.test; /** * 聯系方式類型 */ public enum ContactType { EMAIL,PHONE,MOBILE,WEBSITE }
用戶User:
package com.annotation.test; public class User { /** * 姓名 */ private String name; /** * 郵箱 */
@ContactValidator(type = ContactType.EMAIL)
private String email; /** * 電話 */
@ContactValidator(type = ContactType.PHONE)
private String phone; /** * 手機號 */
@ContactValidator(type = ContactType.MOBILE)
private String mobile; /** * 網址 */
@ContactValidator(type = ContactType.WEBSITE)
private String website; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getWebsite() { return website; } public void setWebsite(String website) { this.website = website; } }
校驗工具類:
package com.annotation.test; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ValidatorUtil { /** * 校驗郵箱 * * @param email * @return */ public static boolean isValidEmail(String email) { Pattern p =Pattern.compile(".+@.+\\.[a-z]+"); Matcher m =p.matcher(email); return m.matches(); } /** * 校驗電話 * * @param phone * @return */ public static boolean isValidPhone(String phone) { Pattern p =Pattern.compile("\\d\\d([,\\s])?\\d\\d\\d\\d([,\\s])?\\d\\d\\d\\d"); Matcher m =p.matcher(phone); return m.matches(); } /** * 校驗手機號 * * @param mobile * @return */ public static boolean isValidMobile(String mobile) { Pattern p =Pattern.compile("^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$"); Matcher m =p.matcher(mobile); return m.matches(); } /** * 校驗網址 * * @param website * @return */ public static boolean isValidWebsite(String website){ Pattern p =Pattern.compile("^(https?|ftp|file)://.+$"); Matcher m =p.matcher(website); return m.matches(); } }
解析器:
package com.annotation.test; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class FieldAnnotationParser { /** * 解析器 * @param user * @throws IllegalArgumentException * @throws IllegalAccessException */ public static void parser(User user)throws IllegalArgumentException, IllegalAccessException { //獲取所有字段 Field[]fields = user.getClass().getDeclaredFields(); for(Field field : fields){ Annotation[] annotations = field.getAnnotations();//獲取字段上的所有注解 for(Annotation annotation : annotations){ //如果是ContactValidator注解 if(annotation instanceof ContactValidator){ ContactValidator contactValidator = (ContactValidator) annotation; if(field.getModifiers() == Modifier.PRIVATE){//如果是私有字段,設置反射的對象在使用時取消Java語言訪問檢查 field.setAccessible(true); } boolean result =false;//標識變量 //獲取字段值 String fieldValue = (String) field.get(user); switch (contactValidator.type()) { case EMAIL: result =ValidatorUtil.isValidEmail(fieldValue); break; case PHONE: result =ValidatorUtil.isValidPhone(fieldValue); break; case MOBILE: result =ValidatorUtil.isValidMobile(fieldValue); break; case WEBSITE: result =ValidatorUtil.isValidWebsite(fieldValue); break; } if(!result){ System.out.println("Invalid " + field.getName() + ": " +fieldValue); } } } } } }
測試類:
package com.annotation.test; public class AnnotationTest { /** *主函數 * @param args * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void main(String[] args)throws IllegalArgumentException,IllegalAccessException { User user =new User(); user.setName("TimSturt"); user.setPhone("0931234 3819"); //錯誤的電話格式 user.setMobile("13575309630"); //正確的手機格式 user.setEmail("test@gmail.com"); //正確郵箱格式 user.setWebsite("fttp://test.com"); //錯誤的網站url FieldAnnotationParser.parser(user); } }
輸出如下:
Invalid phone: 0931234 3819 Invalid website: fttp://test.com
解說:校驗工具類中的正則表達式不適用於所有情況,可自行調整
結語:
在Java中元數據以標簽的形式存在於Java代碼中,元數據標簽的存在並不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或在運行時知道被運行代碼的描述信息。
針對前述內容進行簡述:
第一、元數據以標簽的形式存在於Java代碼中。
第二、元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。
第三、元數據需要編譯器之外的工具進行額外的處理用來生成其它的程序部件。
第四、元數據可以只存在於Java源代碼級別,也可以存在於編譯之后的Class文件內部。
要點總結:
(1)Annotation定義語法為:
modifiers @interface AnnotationName { element declaration1 element declaration2 . . . }
modifiers指:public,protected,private或者默認值(什么也沒有)。
(2)一個元素的聲明(element declaration):
type elementName();
或者
type elementName() default value;
(3)可以通過如下方式來使用Annotation:
@AnnotationName(elementName1=value1, elementName2=value2, . . .),元素聲明的順序沒有關系,有默認值的元素可以不列在初始化表中,此時它們使用默認值。
A、根據上述描述,下述定義的三個Annotation是等價的:
@BugReport(assignedTo="Harry", severity=0)
@BugReport(severity=0, assignedTo="Harry")
@BugReport(assignedTo="Harry")
B、如果只有一個元素,最好將參數名稱設為value,賦值時不用指定名稱而直接賦值,如下:
AnnotationName(“somevalue”)
C、如果Annotation的元素是數組,則可以做如下聲明:
@BugReport(. . ., reportedBy={"Harry", "Carl"})
D、如果數組中只有一個元素時可以做如下聲明:
@BugReport(. . ., reportedBy="Joe") // OK, same as {"Joe"}
E、如果Annotation元素類型為Annotation時可以做如下聲明:
@BugReport(testCase=@TestCase(name="free"), . . .)
(4)Annotation中元素的類型必須是下述類型或者這些類型的組合(下述類型構成的數組):
基本類型 (int, short, long, byte, char, double, float, boolean)、字符串(String)、類、枚舉類型(enum)、Annotation類型
參考資料推薦
http://mp.weixin.qq.com/s?__biz=MzA3ODY0MzEyMA==&mid=2657236022&idx=1&sn=e23ccaf9fa05619910e404f9c0f4e8e4&scene=0#wechat_redirect
