代理、反射、註解、hook
- 2020 年 3 月 27 日
- 筆記
代理
通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,擴展目標對象的功能。 代理對象攔截真實對象的方法調用,在真實對象調用前/後實現自己的邏輯調用 這裡使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴展該方法。
動態代理的用途與裝飾模式很相似,就是為了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。

/** * subject(抽象主題角色): * 真實主題與代理主題的共同接口。 */ interface Subject { void sellBook(); } /** * ReaISubject(真實主題角色): * 定義了代理角色所代表的真實對象。 */ public class RealSubject implements Subject { @Override public void sellBook() { System.out.println("出版社賣書"); } } /** * Proxy(代理主題角色): * 含有對真實主題角色的引用,代理角色通常在將客戶端調用傳遞給真實主題對象之前或者之後執行某些操 作,而不是單純返回真實的對象。 */ public class ProxySubject implements Subject { private RealSubject realSubject; @Override public void sellBook() { if (realSubject == null) { realSubject = new RealSubject(); } sale(); realSubject.sellBook(); give(); } public void sale() { System.out.println("打折"); } public void give() { System.out.println("送優惠券"); } } public class Main { public static void main(String[] args) { //靜態代理(我們自己靜態定義的代理類) ProxySubject proxySubject = new ProxySubject(); proxySubject.sellBook(); //動態代理(通過程序動態生成代理類,該代理類不是我們自己定義的。而是由程序自動生成) RealSubject realSubject = new RealSubject(); MyHandler myHandler = new MyHandler(); myHandler.setProxySubject(realSubject); Subject subject = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), myHandler); subject.sellBook(); } } public class MyHandler implements InvocationHandler { private RealSubject realSubject; public void setProxySubject(RealSubject realSubject) { this.realSubject = realSubject; } /** * @param proxy 指代我們所代理的那個真實對象 * @param method 指代的是我們所要調用真實對象的某個方法的Method對象 * @param args 指代的是調用真實對象某個方法時接受的參數 * @reurn * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { sale(); proxy = method.invoke(realSubject, args); give(); return proxy; } public void sale() { System.out.println("打折"); } public void give() { System.out.println("送優惠券"); } }
HOOK
Hook(鉤子): Android 操作系統中系統維護着自己的一套事件分發機制,那麼Hook就是在事件傳送到終點前截獲並監控事件的傳輸,並修改事件流程的過程。
public static void showToast(Context context, CharSequence cs, int length) { Toast toast = Toast.makeText(context,cs,length); hook(toast); toast.show(); }
Hook 的選擇點: 靜態變量和單例,因為一旦創建對象,它們不容易變化,非常容易定位。 Hook 過程: 尋找 Hook 點,原則是靜態變量或者單例對象,盡量 Hook public 的對象和方法。 選擇合適的代理方式,如果是接口可以用動態代理。 偷梁換柱——用代理對象替換原始對象。 Android 的 API 版本比較多,方法和類可能不一樣,所以要做好 API 的兼容工作
應用
- Hook指定應用注入廣告
- 修復bug
- App登錄劫持
登錄界面上面的用戶信息都存儲在EditText控件上,然後通過用戶手動點擊「登錄」按鈕才會將上面的信息發送至服務器端去驗證賬號與密碼是否正確。這樣就很簡單了,黑客們只需要找到開發者在使用EditText控件的getText方法後進行網絡驗證的方法,Hook該方法,就能劫持到用戶的賬戶與密碼了
AOP(AspectJ)
正如面向對象編程是對常見問題的模塊化一樣,面向切面編程是對橫向的同一問題進行模塊化,比如在某個包下的所有類中的某一類方法中都需要解決一個相似的問題,可以通過AOP的編程方式對此進行模塊化封裝,統一解決 關於AOP的具體解釋,可以參照維基百科。而AspectJ就是面向切面編程在Java中的一種具體實現。
Join point:程序中執行代碼插入的點,例如方法調用時或者方法執行時。
AOP編程的具體使用場景 日誌記錄 持久化 行為監測 數據驗證 緩存 …
比如埋點,記錄方法執行的時長。可以定義註解。aop可以過濾所有被"這個註解"標記的方法和構造器。然後可以可以根據他提供的方法(註解),講我們想要埋點的日誌插入進去。
註解和反射的區別 反射:對於任何一個對象,都能夠調用它的任何一個方法和屬性,包括私有的。這種動態獲取的方法就叫反射。 註解:降低項目的耦合度;自動完成一些規律性的代碼;自動生成java代碼,減輕開發者的工作量。 而註解需要用到反射: 定義註解,使用註解,讀取註解(用到反射)
Annotation Processing Tool(APT),註解處理器,javac中用於編譯時掃描和解析Java註解的工具。 在編譯階段執行的,它的原理就是讀入Java源代碼,解析註解,然後生成新的Java代碼。新生成的Java代碼最後被編譯成Java位元組碼,註解解析器不能改變讀入的Java 類,比如不能加入或刪除Java方法。
//綁定一個View (View不能為private 或者static)否則就會通過反射去獲取 @BindView(R.id.textview)
註解和反射效率問題 hook和aop類似 反射先new類class,然後在從類裏面new對象。Class.getMethod(…)還要查找所有的方法。 而註解編譯期間就完成了註解的反射工作, jvm只是讀取。
反射的缺點 不安全 編譯器沒法對反射相關的代碼做優化 慢的原因還有安全檢查,訪問控制等。比如說這個方法能不能獲得,能不能執行等,傳進的參數的類型檢查等。
註解的用法
獲取類的註解 URLBuilder.Path path = paramEntity.getClass().getAnnotation(URLBuilder.Path.class);
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
獲取方法的註解 Method.getAnnotations(); 是獲取方法上面對應的註解。method.getGenericParameterTypes();獲取的是方法參數的類型,裏面帶有實際的參數類型。 method.getParameterAnnotations();獲取的是方法參數上面的註解,是一個二維數組,第一個維度代表的是方法參數對應的下標,比如,一個方法有3個參數,那0代表第一個參數,1代表第二個參數,2代表第三個參數。
@Retention:註解保留的生命周期 @Target:註解對象的作用範圍。 創建一個註解遵循: public @interface 註解名 {方法參數}
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface getViewTo { int value() default -1; } public class MainActivity extends AppCompatActivity { @getViewTo(R.id.textview) private TextView mTv; @getViewTo(R.id.button) private Button mBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //通過註解生成View; getAllAnnotationView(); } /** * 解析註解,獲取控件 */ private void getAllAnnotationView() { //獲得成員變量 Field[] fields = this.getClass().getDeclaredFields(); for (Field field : fields) { try { //判斷註解 if (field.getAnnotations() != null) { //確定註解類型 if (field.isAnnotationPresent(GetViewTo.class)) { //允許修改反射屬性 field.setAccessible(true); GetViewTo getViewTo = field.getAnnotation(GetViewTo.class); //findViewById將註解的id,找到View注入成員變量中 field.set(this, findViewById(getViewTo.value())); } } } catch (Exception e) { } } } }
反射機制
JAVA反射機制是在運行狀態中,對於任意一個類 (class文件),都能夠知道這個類的所有屬性和方法; 對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。 動態獲取類中信息,就是java反射 。可以理解為對類的解剖。
Person
public class Person { private int age; private String name; public Person(String name,int age) { super(); this.age = age; this.name = name; System.out.println("Person param run..."+this.name+":"+this.age); } public Person() { super(); System.out.println("person run"); } public void show(){ System.out.println(name+"...show run..."+age); } private void privateMethod(){ System.out.println(" method run "); } public void paramMethod(String str,int num){ System.out.println("paramMethod run....."+str+":"+num); } public static void staticMethod(){ System.out.println(" static method run......"); } }
要想要對位元組碼文件進行解剖,必須要有位元組碼文件對象. 如何獲取位元組碼文件對象呢? 獲取Class對象的三種方式
public class ReflectDemo { /** * @param args * @throws ClassNotFoundException */ public static void main(String[] args) throws ClassNotFoundException { getClassObject_3(); } /* * 獲取位元組碼對象的方式: * Object類中的getClass()方法的。 * 想要用這種方式,必須要明確具體的類,並創建對象。 * 麻煩 . * */ public static void getClassObject_1(){ Person p = new Person(); Class clazz = p.getClass(); Person p1 = new Person(); Class clazz1 = p1.getClass(); System.out.println(clazz==clazz1); } /* * 方式二: * 任何數據類型都具備一個靜態的屬性.class來獲取其對應的Class對象。 * 相對簡單,但是還是要明確用到類中的靜態成員。 * 還是不夠擴展。 * */ public static void getClassObject_2() { Class clazz = Person.class; Class clazz1 = Person.class; System.out.println(clazz==clazz1); } /* * 方式三: * 只要通過給定的類的 字符串名稱就可以獲取該類,更為擴展。 * 可是用Class類中的方法完成。 * 該方法就是forName. * 這種方式只要有名稱即可,更為方便,擴展性更強。 */ public static void getClassObject_3() throws ClassNotFoundException { String className = "cn.test.bean.Person"; Class clazz = Class.forName(className); System.out.println(clazz); } }
獲取Class中的構造函數
public class ReflectDemo2 { /** * @param args * @throws Exception * @throws InstantiationException * @throws ClassNotFoundException */ public static void main(String[] args) throws ClassNotFoundException, InstantiationException, Exception { createNewObject_2(); } public static void createNewObject_2() throws Exception { // cn.test.bean.Person p = new cn.test.bean.Person("小強",39); /* * 當獲取指定名稱對應類中的所體現的對象時, * 而該對象初始化不使用空參數構造該怎麼辦呢? * 既然是通過指定的構造 函數進行對象的初始化, * 所以應該先獲取到該構造函數。 通過位元組碼文件對象即可完成。 * 該方法是:getConstructor(paramterTypes); * */ String name = "cn.test.bean.Person"; //找尋該名稱類文件,並加載進內存,併產生Class對象。 Class clazz = Class.forName(name); //獲取到了指定的構造函數對 象。 Constructor constructor = clazz.getConstructor(String.class,int.class); //通過該構造器對象的newInstance方法進行對象的初始化。 Object obj = constructor.newInstance("小明",38); } public static void createNewObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException{ //早期:new時候,先根據被new的類的名稱找尋該類的位元組碼文件,並加載進內存, // 並創建該位元組碼文件對象,並接着創建該位元組文件的對應的Person對象. // cn.test.bean.Person p = new cn.test.bean.Person(); //現在: String name = "cn.test.bean.Person"; //找尋該名稱類文件,並加載進內存,併產生Class對象。 Class clazz = Class.forName(name); //如何產生該類的對象呢? Object obj = clazz.newInstance(); } }
獲取Class中的字段
/* * 獲取位元組碼文件中的字段。 */ public static void getFieldDemo() throws Exception { Class clazz = Class.forName("cn.test.bean.Person"); Field field = null;//clazz.getField("age");//只能獲取公有的, field = clazz.getDeclaredField("age");//只獲取本類,但包含私有。 //對私有字段的訪問取消權限檢查。暴力訪問。 field.setAccessible(true); Object obj = clazz.newInstance(); field.set(obj, 89); Object o = field.get(obj); System.out.println(o); // cn.test.bean.Person p = new cn.test.bean.Person(); // p.age = 30; }
獲取Class中的方法
public static void getMethodDemo_3() throws Exception { Class clazz = Class.forName("cn.test.bean.Person"); Method method = clazz.getMethod("paramMethod", String.class,int.class); Object obj = clazz.newInstance(); method.invoke(obj, "小強",89); } public static void getMethodDemo_2() throws Exception { Class clazz = Class.forName("cn.test.bean.Person"); Method method = clazz.getMethod("show", null);//獲取空參數一般方法。 // Object obj = clazz.newInstance(); Constructor constructor = clazz.getConstructor(String.class,int.class); Object obj = constructor.newInstance("小明",37); method.invoke(obj, null); } /* * 獲取指定Class中的所有公共函數。 */ public static void getMethodDemo() throws Exception { Class clazz = Class.forName("cn.test.bean.Person"); Method[] methods = clazz.getMethods();//獲取的都是公有的方法。 methods = clazz.getDeclaredMethods();//只獲取本類中所有方法,包含私有。 for(Method method : methods){ System.out.println(method); } }
如何獲得泛型類的真實類型 通過Class類上的 getGenericSuperclass() 或者 getGenericInterfaces() 獲取父類或者接口的類型,然後通過ParameterizedType.getActualTypeArguments()
public class RealType<T>{ private Class<T> clazz; // 使用反射技術得到T的真實類型 public Class getRealType(){ // 獲取當前new的對象的泛型的父類類型 ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); // 獲取第一個類型參數的真實類型 this.clazz = (Class<T>) pt.getActualTypeArguments()[0]; return clazz; } }