反射、注解與依賴注入總結
上一篇【線程、多線程與線程池總結】中主要記錄線程、多線程相關概念,側重于線程的Future使用與線程池的操作;同樣這一篇【反射、注解與依賴注入總結】依然著重于相關概念與使用。
現在在我們構建自己或公司的項目中,或多或少都會依賴幾個流行比較屌的第三方庫,比如:Butter Knife、Retrofit、Dagger 2等,如果你沒用過,那你需要找時間補一下啦;有時在使用后我們會好奇他們到底是怎么做到這種簡潔、高效、松耦合等諸多優點的,當然這里我不探討它們具體怎么實現的,而關心的是它們都用到同樣的技術反射和注解,并實現的依賴注入。
如果你好奇這些庫具體是怎么實現的,或者想了解他們實現的原理,這里向你推薦幾篇文章:
1、android注解Butterknife的使用及代碼分析
2、Retrofit源碼1: 為什么寫一個interface就可以實現http請求
3、Retrofit分析-漂亮的解耦套路
4、Dagger 源碼解析
5、Android:dagger2讓你愛不釋手-基礎依賴注入框架篇
6、Android:dagger2讓你愛不釋手-重點概念講解、融合篇
7、Android:dagger2讓你愛不釋手-終結篇
這些好文章已經幫你收藏了,下面直接進入我的主題【反射、注解與依賴注入總結】。
● 反射(Reflection)
反射的概念
主要是指程序可以訪問,檢測和修改它本身狀態或行為的一種能力,并能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
概念看著就有些暈或不知所云啦,可以通過反射的作用理解它的概念。
反射的作用
反射可以讓我們在運行時獲取類的屬性,方法,構造方法、父類、接口等信息,通過反射還可以讓我們在運行期實例化對象、調用方法、即使方法或屬性是私有的的也可以通過反射的形式調用。
所有為什么第三方庫基本都會使用到反射,正是因為反射這種 “看透 Class” 的能力。
反射相關的類、方法
要看透一個類,首先要獲取這個類的對象,其它信息都是通過這個對象獲取的,下面的所有的示例具體操作代碼請參考 【個人學習項目DroidStudy】,我在這個工程下新建一個 ReflectionActivity,包的路徑為 com.sun.study.ui.activity.ReflectionActivity,通過反射相關的類、方法讓我看透這個類。
1、獲取對象的三種方式:
第一種、知道一個類,直接獲取 Class 對象
Class<?> cls1 = ReflectionActivity.class;
第二種、如果已經得到了某個對象,可以通過這個對象獲取 Class 對象
ReflectionActivity activity = new ReflectionActivity();
Class<?> cls2 = activity.getClass();
第三種、如果你在編譯期獲取不到目標類型,但是你知道它的完整類路徑,那么你可以通過如下的形式來獲取 Class 對象,這樣獲取可能會拋出異常 ClassNotFoundException。
try {
Class<?> cls3 = Class.forName("com.sun.study.ui.activity.ReflectionActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2、反射的相關方法和示例
列出反射的相關方法
getName():獲得類的完整名字。
newInstance():通過類的不帶參數的構造方法創建這個類的一個對象。
getFields():獲得類的public類型的屬性。
getDeclaredFields():獲得類的所有屬性。
getMethods():獲得類的public類型的方法。
getDeclaredMethods():獲得類的所有方法。
getMethod(String name, Class[] parameterTypes):獲得類的特定方法。
getModifiers()和Modifier.toString():獲得屬修飾符,例如private,public,static等
getReturnType():獲得方法的返回類型
getParameterTypes():獲得方法的參數類型
getConstructors():獲得類的public類型的構造方法。
getConstructor(Class[] parameterTypes):獲得類的特定構造方法。
getSuperclass():獲取某類的父類
getInterfaces():獲取某類實現的接口
示例一:獲得類的所有方法(Method)信息
private void getMethodsInfo() {
Class<ReflectionActivity> cls = ReflectionActivity.class;
Method[] methods = cls.getDeclaredMethods();
if (methods == null) return;
StringBuilder sb = new StringBuilder();
for (Method method:methods) {
sb.append(Modifier.toString(method.getModifiers())).append(" ");
sb.append(method.getReturnType()).append(" ");
sb.append(method.getName()).append("(");
Class[] parameters = method.getParameterTypes();
if (parameters != null) {
for (int i=0; i<parameters.length; i++) {
Class paramCls = parameters[i];
sb.append(paramCls.getSimpleName());
if (i < parameters.length - 1) sb.append(", ");
}
}
sb.append(")\n\n");
}
tvInfo.setText(sb.toString());
}
運行結果如下圖:
reflection_icon1.png
示例一:獲得類的所有屬性(Field)信息,并修改類型Int屬性i的值
private void modifyFieldValue() {
Class<ReflectionActivity> cls = ReflectionActivity.class;
Field[] fields = cls.getDeclaredFields();
if (fields == null) return;
StringBuilder sb = new StringBuilder();
sb.append("獲得類的所有屬性信息:\n\n");
for (Field field:fields) {
sb.append(Modifier.toString(field.getModifiers())).append(" ");
sb.append(field.getType().getSimpleName()).append(" ");
sb.append(field.getName()).append(";");
sb.append("\n\n");
}
try {
sb.append("屬性i的默認值:i = ");
Field f = cls.getDeclaredField("i");
sb.append(f.getInt("i")).append("\n\n");
f.set("i", 100);
sb.append("屬性i修改后的值:i = ");
sb.append(f.getInt("i")).append("\n\n");
} catch (Exception e) {
e.printStackTrace();
}
tvInfo.setText(sb.toString());
toolbar.setSubtitle("修改類型Int屬性i的值");
}
運行結果如下圖:
reflection_icon2.png
更多示例請參考 【個人學習項目DroidStudy】
反射的相關內容先記錄到這,接下來看看注解相關概念與使用。
● 注解(Annotation)
注解的概念
注解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK 1.5及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,注釋。
注解的作用
1、標記作用,用于告訴編譯器一些信息讓編譯器能夠實現基本的編譯檢查,如@Override、Deprecated,看下它倆的源碼
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}
2、編譯時動態處理,動態生成代碼,如Butter Knife、Dagger 2
3、運行時動態處理,獲得注解信息,如Retrofit
注解的分類
注解的分類有兩種分法:
第一種分法
1、基本內置注解,是指Java自帶的幾個Annotation,如@Override、Deprecated、@SuppressWarnings等
2、元注解(meta-annotation),是指負責注解其他注解的注解,JDK 1.5及以后版本定義了4個標準的元注解類型,如下:
1、@Target
2、@Retention
3、@Documented
4、@Inherited
3、自定義注解,根據需要可以自定義注解,自定義注解需要用到上面的meta-annotation
第二種分法,根據作用域分類
1、源碼時注解(RetentionPolicy.SOURCE)
2、編譯時注解(RetentionPolicy.CLASS)
3、運行時注解(RetentionPolicy.RUNTIME)
注解相關知識點
1、元注解相關信息
@Target:指Annotation所修飾的對象范圍,通過ElementType取值有8種,如下
TYPE:類、接口(包括注解類型)或枚舉
FIELD:屬性
METHOD:方法
PARAMETER:參數
CONSTRUCTOR:構造函數
LOCAL_VARIABLE:局部變量
ANNOTATION_TYPE:注解類型
PACKAGE:包
@Retention:指Annotation被保留的時間長短,通過RetentionPolicy取值有3種,如下:
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在運行時有效(即運行時保留)
@Documented:是一個標記注解,用于描述其它類型的注解應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。
@Inherited:也是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的
2、注解定義格式
public @interface 注解名 { 定義體 }
3、注解參數可支持的數據類型:
8種基本數據類型 int、float、boolean、byte、double、char、long、short
String、Class、enum、Annotation
以上所有類型的數組
4、?注意:自定義注解如果只有一個參數成員,最好把定義體參數名稱設為"value",如@Target
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
看一個示例
具體要求和運行結果都在下面這張圖上顯示出來了,貼下圖
annotation_icon.png
再貼三塊代碼,首先是自定義注解代碼:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RequestAnnotation {
boolean withDialog() default true;
String withMessage() default "正在加載,請稍后...";
}
其次是執行模擬的網絡請求,核心代碼是通過上面的反射和注解完成的;具體詳細代碼請參考 【個人學習項目DroidStudy】,下次使用動態代理和Google的dexmaker完成這個功能,敬請關注,如果你對線程池還不清晰請參考我以前的文章【線程、多線程與線程池總結】。貼下核心代碼:
// 線程池
private static ExecutorService pool = Executors.newCachedThreadPool();
// 模擬處理網絡請求
public boolean process(final Class<?> clazz, String methodName, final Object... args) throws Exception {
Class[] argsClass = getClazzByArgs(args);
final Method method = clazz.getDeclaredMethod(methodName, argsClass);
if (method == null) {
sendMsg(TYPE_ERROR);
return false;
}
// 獲取注解信息
RequestAnnotation annotation = method.getAnnotation(RequestAnnotation.class);
if (annotation != null && annotation.withDialog()) {
loadingDialog.show(annotation.withMessage());
}
pool.execute(new Runnable() {
@Override
public void run() {
try {
method.setAccessible(true);
method.invoke(clazz.newInstance(), args);
sendMsg(TYPE_SUCCESS);
} catch (Exception e) {
e.printStackTrace();
}
}
});
return true;
}
最后是調用網絡請求接口:
@RequestAnnotation(withDialog = false, withMessage = "正在加載,請稍后...")
public void apiTestFunc(String param1, String param2) {
try {
// 模擬網絡請求的耗時操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 點擊執行的代碼
DynamicProxyUtil proxyUtil = new DynamicProxyUtil(AnnotationActivity.this);
proxyUtil.process(RequestNetworkApi.class, "apiTestFunc", "參數一", "參數二");
● 依賴注入(Dependency Injection)
依賴注入(Dependency Injection):可以通過這個服務來安全的注入組件到應用程序中,在應用程序部署的時候還可以選擇從特定的接口屬性進行注入。
看完上面反射和注解的記錄后,可以更好的理解依賴注入,如果你不用那些第三方的注入庫你也在經常用到依賴注入,比如下面這一段從codekk上截取的代碼:
public class Human {
...
Father father;
...
public Human(Father father) {
this.father = father;
}
}
上面代碼中,我們將 father 對象作為構造函數的一個參數傳入。在調用 Human 的構造方法之前外部就已經初始化好了 Father 對象。像這種非自己主動初始化依賴,而通過外部來傳入依賴的方式,我們就稱為依賴注入。
依賴注入的實現有多種途徑,而在 Java 中,使用注解是最常用的。比如通過Butter Knife、Dagger依賴注入庫實現,都是使用注解來實現依賴注入,但它利用 APT(Annotation Process Tool) 在編譯時生成輔助類,這些類繼承特定父類或實現特定接口,程序在運行時加載這些輔助類,調用相應接口完成依賴生成和注入。
依賴注入在這里僅僅剖析下概念,有時間將會補一個例子,暫且到這吧。
文/孫福生(簡書作者)
原文鏈接:http://www.jianshu.com/p/24820bf3df5c