2背景
最近在為公司的技術改造做準備,我設計了一個提高Web開發效率的技術框架,為了增加框架的友好性和易用性,決定采用注解來代替配置文件,于是我查詢了很多的資料,進行整理和學習。
2概念
注解是JDK5引入的新特性,最初衍生自代碼注釋,但現在早已經超出了注釋的范疇,以至于我很惶恐,不敢使用注釋這個詞匯來描述他,盡管現有的很多資料里仍然稱其為注釋。如果說反射使得很多技術實現(動態代理、依賴注入等)有了基礎,那么注解就是使這些技術實現變得平民化的基礎。
從class文件規范中可以看出,JDK5開始,class文件已經引入了注解描述片段。站在java虛擬機的角度來看,class保留和運行時保留的注解已經和java二進制碼放在了同等的地位。虛擬機在加載class文件時,會為注解內容分配空間并進行解析,最終還會為注解和對應的二進制碼建立關聯。盡管這些注解不會被運行,但其對代碼的說明能力,結合反射技術已經足夠我們做太多的事情。
我們知道,java除了內置的注解(@Override、@Deprecated等)以外,還支持自定義注解(Struts、Hibernate等很多框架甚至java自身都實現了很多自定義注解)。當然,更為厲害的是元注解,元注解是用來描述注解的注解(光聽著就覺得厲害了吧)。
要實現一個自定義注解,必須通過@interface關鍵字來定義。且在@interface之前,需要通過元注解來描述該注解的使用范圍(@Target)、生命周期(@Retention)及其他(其他的不重要,所以領盒飯了)。
@Target用于描述注解的使用范圍(即:被描述的注解可以用在什么地方),其取值有:
取值
描述
CONSTRUCTOR
用于描述構造器(領盒飯)。
FIELD
用于描述域(領盒飯)。
LOCAL_VARIABLE
用于描述局部變量(領盒飯)。
METHOD
用于描述方法。
PACKAGE
用于描述包(領盒飯)。
PARAMETER
用于描述參數。
TYPE
用于描述類或接口(甚至enum)。
@Retention用于描述注解的生命周期(即:被描述的注解在什么范圍內有效),其取值有:
取值
描述
SOURCE
在源文件中有效(即源文件保留,領盒飯)。
CLASS
在class文件中有效(即class保留,領盒飯)。
RUNTIME
在運行時有效(即運行時保留)。
根據上述介紹,如果我需要定義一個用于對方法進行描述,且能在運行時可以讀取到的自定義注解(假定我希望這個注解的名字是Sample)。那么,我就應該這樣:
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface Sample {public String value() default "";}
OK,自定義注解已經寫好了,那我們就可以在代碼中使用這個注解了,如:
@Sample(value="I'm here.")
public void anyName() {
... ...
}
值得一提的是,在網上能搜索到的資料(中文的)幾乎都是到此為止了。給人的感覺就像看美國大片,每到結束的時候總會給你一種未完待續的意味。事實上,我能容忍電影給我這樣的感覺,因為這樣會讓我充滿期待。而從技術的角度來說,我很厭惡這種感覺。
事實上,事情遠沒有結束。如果自定義注解以這樣的形式存在,那么這種存在是沒有任何實際意義的。
那么,我們接下來該做什么呢?
接下來我們應該編寫自己的注解處理器。
嗯,再啰嗦一下,提到注解處理器,我又被N多資料誤導了。很多資料都提到APT,或者AbstractProcessor。但事實上,我的理解是APT或者AbstractProcessor更多的用于:在非運行時進行增強處理(如:分析邏輯BUG,分析代碼結構等等)。
回到注解處理器,注釋處理器其實就是一段用于解釋或處理自定義注解的代碼而已,沒有太多復雜的概念或者技術(嗯,先賣個關子,后面的實例會細說注解處理器的)。
2實踐
通過前文對自定義注解的了解,我猜想我應該這樣做:
1.結合實際需求規劃注解的功能,以及定義如何解析注解
先說說我的需求吧:框架會把頁面劃分成N個分塊,而每個分塊都需要不同的類來處理輸出內容,處理到不同的分塊是,框架會自動創建對應的類實例(目前為止,沒有任何問題)。接下來的問題就來了,每個分塊處理類處理分塊內容時,所需要的參數是不一樣的(參數類型以及參數個數都不一樣);因此,也不好定義一個固定的接口。當然,肯定有人會說可以把參數改成map,或Object數組。是的,這是一種解決辦法,但是如果我用自定義注解,會不會能更好的完成這項工作呢?是的,答案在你我心中。
我們不妨設想一下:
如果處理類需要獲取參數,那么這個處理類就給我注解某個方法(方法名任意,前文提到過:虛擬機會做好二者之間的關聯),以說明該方法需要被框架預先調用一次(類似初始化方法)。同樣的道理,在注解這個方法時,加入所需要的參數注解。
然后,在框架的處理程序中,我們先根據注解查找方法,如果該方法存在,則再次根據注解把對應的參數準備好,然后反射調用invoke方法。
OK,這樣的設想應該是行得通的。
2.定義并構造自定義注解
前文提到了我們需要對方法進行注解,而且注解中還需要包含參數信息。好吧,我的設想是定義兩個注解:
@RenderParameter用于描述方法的參數,包括參數類型、參數來源等。
@RenderMethod用于描述方法(主要描述方法的參數列表)。
這里要提到一個小技巧:即注解可以使用數組(嗯嗯,待會會看到的)。
先來定義一下@RenderParameter吧:
……
@Retention(RetentionPolicy.RUNTIME) // 運行時保留
@Target({ElementType.METHOD}) // 注解對象為方法
public @interface RenderParameter {
// 參數類型
public enum ParameterType { STRING, SHORT, INT, BOOL, LONG, OBJECT };
// 參數值的來源
public enum ScopeType { NORMAL, SESSION, COOKIE, ATTRIBUTE, CUSTOM };
public String name(); // 參數名
public boolean ignoreCase() default false; // 匹配時是否忽略大小寫
public ParameterType type() default ParameterType.STRING; //參數類型
public ScopeType scope() default ScopeType.NORMAL; //參數值來源
}
再看看@RenderMethod的定義:
……
@Retention(RetentionPolicy.RUNTIME) // 運行時保留@Target({ElementType.METHOD})// 注解對象為方法public @interface RenderMethod { public enum MethodType { INQUIRE }; public MethodType method() default MethodType.INQUIRE;public RenderParameter[] parameters();// 參數列表 } 至此,兩個自定義注解已經完成,看看我應該如何使用他們:@RenderMethod(parameters={@RenderParameter(name="logined", scope=ScopeType.SESSION),@RenderParameter(name="loginedUser", scope=ScopeType.SESSION)})public void inquire(String logined, String loginedUser) {if("true".equals(logined)) {write(loginedUser + " is logined.");} else {write("No user logined.");}}
3.構造自定義注解的處理方法(即注解處理器)
終于又說到注解處理器了,其實很簡單:
……
// 此處的renderer就是采用了自定義注解的類實例for(Method method : renderer.getClass().getDeclaredMethods()) {RenderMethod rm = (RenderMethod)method.getAnnotation(RenderMethod.class); if(rm != null) {int length = rm.parameters().length;Object[] parameters = length > 0 ? buildParameters(rm.parameters()) : null; try {method.invoke(renderer, parameters);} catch (IllegalArgumentException e) {log.error(e.getMessage());} catch (IllegalAccessException e) {log.error(e.getMessage());} catch (InvocationTargetException e) {log.error(e.getMessage());} break;}}… …
// 根據注解數組創建參數對象列表,供invoke使用private Object[] buildParameters(RenderParameter[] parameters) {Object[] objs = new Object[parameters.length];int i = 0; for(RenderParameter parameter : parameters) {ScopeType scope = parameter.scope(); // 參數值來自request.getParameterif(scope == ScopeType.NORMAL) {String temp = request.getParameter(parameter.name());String value = null; if(temp != null && !"".equals(temp)) {try {byte[] bytes = temp.getBytes("iso-8859-1");value = new String(bytes, "UTF-8");} catch (UnsupportedEncodingException e) {log.error(e.getMessage());}} objs[i ++] = value; // 參數值來自Session} else if(scope == ScopeType.SESSION) {objs[i ++] = request.getSession().getAttribute(parameter.name()); // 參數值來自Cookie} else if(scope == ScopeType.COOKIE) {for(Cookie cookie : request.getCookies()) {if(cookie.getName().equals(parameter.name())) {objs[i ++] = cookie.getValue();break;}} // 參數值來自request. getAttribute} else if(scope == ScopeType.ATTRIBUTE) {objs[i ++] = request.getAttribute(parameter.name());}} return objs;}
看文倉www.kanwencang.com網友整理上傳,為您提供最全的知識大全,期待您的分享,轉載請注明出處。
歡迎轉載:http://www.kanwencang.com/bangong/20170104/81425.html
文章列表