前言:
最近學習了EventBus、BufferKinfe、GreenDao、Retrofit 等優秀開源框架,它們新版本無一另外的都使用到了注解的方式,我們使用在使用的時候也嘗到不少好處,基於這種想法我覺得有必要對注解有個更深刻的認識,今天中午把公司的項目搞完了,晚上加個班學習總結一下Java的注解。
什么是注解?
對於很多初次接觸的開發者來說應該都有這個疑問?Annontation是Java5開始引入的新特征,中文名稱叫注解。它提供了一種安全的類似注釋的機制,用來將任何的信息或元數據(metadata)與程序元素(類、方法、成員變量等)進行關聯。為程序的元素(類、方法、成員變量)加上更直觀更明了的說明,這些說明信息是與程序的業務邏輯無關,並且供指定的工具或框架使用。Annontation像一種修飾符一樣,應用於包、類型、構造方法、方法、成員變量、參數及本地變量的聲明語句中。
注解的用處:
1、生成文檔。這是最常見的,也是java 最早提供的注解。常用的有@param @return 等
2、跟蹤代碼依賴性,實現替代配置文件功能。比如Dagger 2依賴注入,未來java開發,將大量注解配置,具有很大用處;
3、在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出。
元注解:
java.lang.annotation提供了四種元注解,專門注解其他的注解:
@Documented –注解是否將包含在JavaDoc中
@Retention –什么時候使用該注解
@Target –注解用於什么地方
@Inherited – 是否允許子類繼承該注解
1.)@Retention– 定義該注解的生命周期
- RetentionPolicy.SOURCE : 在編譯階段丟棄。這些注解在編譯結束之后就不再有任何意義,所以它們不會寫入字節碼。@Override, @SuppressWarnings都屬於這類注解。
- RetentionPolicy.CLASS : 在類加載的時候丟棄。在字節碼文件的處理中有用。注解默認使用這種方式
- RetentionPolicy.RUNTIME : 始終不會丟棄,運行期也保留該注解,因此可以使用反射機制讀取該注解的信息。我們自定義的注解通常使用這種方式。
舉例:bufferKnife 8.0 中@BindView 生命周期為CLASS
@Retention(CLASS) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); }
2.)Target – 表示該注解用於什么地方。默認值為任何元素,表示該注解用於什么地方。可用的ElementType參數包括
- ElementType.CONSTRUCTOR:用於描述構造器
- ElementType.FIELD:成員變量、對象、屬性(包括enum實例)
- ElementType.LOCAL_VARIABLE:用於描述局部變量
- ElementType.METHOD:用於描述方法
- ElementType.PACKAGE:用於描述包
- ElementType.PARAMETER:用於描述參數
- ElementType.TYPE:用於描述類、接口(包括注解類型) 或enum聲明
舉例Retrofit 2 中@Field 作用域為參數
@Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface Field { String value(); /** Specifies whether the {@linkplain #value() name} and value are already URL encoded. */ boolean encoded() default false; }
3.)@Documented–一個簡單的Annotations標記注解,表示是否將注解信息添加在java文檔中。
4.)@Inherited – 定義該注釋和子類的關系
@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
常見標准的Annotation:
1.)Override
java.lang.Override是一個標記類型注解,它被用作標注方法。它說明了被標注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種注解在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。
2.)Deprecated
Deprecated也是一種標記類型注解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標注的程序元素。所以使用這種修飾具有一定的“延續性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋后的類型或者成員並不是被聲明為@Deprecated,但編譯器仍然要報警。
3.)SuppressWarnings
SuppressWarning不是一個標記類型注解。它有一個類型為String[]的成員,這個成員的值為被禁止的警告名。對於javac編譯器來講,被-Xlint選項有效的警告名也同樣對@SuppressWarings有效,同時編譯器忽略掉無法識別的警告名。
@SuppressWarnings("unchecked")
自定義注解:
這里模擬一個滿足網絡請求接口,以及如何獲取接口的注解函數,參數執行請求。
1.)定義注解:
@ReqType 請求類型
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface ReqType { /** * 請求方式枚舉 * */ enum ReqTypeEnum{ GET,POST,DELETE,PUT}; /** * 請求方式 * @return */ ReqTypeEnum reqType() default ReqTypeEnum.POST; }
@ReqUrl 請求地址
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface ReqUrl { String reqUrl() default ""; }
@ReqParam 請求參數
@Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface ReqParam { String value() default ""; }
從上面可以看出注解參數的可支持數據類型有如下:
1.所有基本數據類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上所有類型的數組
而且不難發現@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。
2.)如何使用自定義注解
public interface IReqApi { @ReqType(reqType = ReqType.ReqTypeEnum.POST)//聲明采用post請求 @ReqUrl(reqUrl = "www.xxx.com/openApi/login")//請求Url地址 String login(@ReqParam("userId") String userId, @ReqParam("pwd") String pwd);//參數用戶名 密碼 }
3.)如何獲取注解參數
這里強調一下,Annotation是被動的元數據,永遠不會有主動行為,但凡Annotation起作用的場合都是有一個執行機制/調用者通過反射獲得了這個元數據然后根據它采取行動。
通過反射機制獲取函數注解信息
Method[] declaredMethods = IReqApi.class.getDeclaredMethods(); for (Method method : declaredMethods) { Annotation[] methodAnnotations = method.getAnnotations(); Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations(); }
也可以獲取指定的注解
ReqType reqType =method.getAnnotation(ReqType.class);
4.)具體實現注解接口調用
這里采用Java動態代理機制來實現,將定義接口與實現分離開,這個后期有時間再做總結。
private void testApi() { IReqApi api = create(IReqApi.class); api.login("whoislcj", "123456"); } public <T> T create(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable {// Annotation[] methodAnnotations = method.getAnnotations();//拿到函數注解數組 ReqType reqType = method.getAnnotation(ReqType.class); Log.e(TAG, "IReqApi---reqType->" + (reqType.reqType() == ReqType.ReqTypeEnum.POST ? "POST" : "OTHER")); ReqUrl reqUrl = method.getAnnotation(ReqUrl.class); Log.e(TAG, "IReqApi---reqUrl->" + reqUrl.reqUrl()); Type[] parameterTypes = method.getGenericParameterTypes(); Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();//拿到參數注解 for (int i = 0; i < parameterAnnotationsArray.length; i++) { Annotation[] annotations = parameterAnnotationsArray[i]; if (annotations != null) { ReqParam reqParam = (ReqParam) annotations[0]; Log.e(TAG, "reqParam---reqParam->" + reqParam.value() + "==" + args[i]); } } //下面就可以執行相應的網絡請求獲取結果 返回結果 String result = "";//這里模擬一個結果 return result; } }); }
打印結果:
以上通過注解定義參數,通過動態代理方式執行函數,模擬了最基本的Retrofit 2中網絡實現原理。