1. 程式人生 > >代理、反射、註解、hook

代理、反射、註解、hook

代理

通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,擴充套件目標物件的功能。
代理物件攔截真實物件的方法呼叫,在真實物件呼叫前/後實現自己的邏輯呼叫
這裡使用到程式設計中的一個思想:不要隨意去修改別人已經寫好的程式碼或者方法,如果需改修改,可以通過代理的方式來擴充套件該方法。

動態代理的用途與裝飾模式很相似,就是為了對某個物件進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。

/**
* 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翻譯就是鉤子。而「鉤子」的意思,就是在事件傳送到終點前截獲並監控事件的傳輸,像個鉤子鉤上事件一樣,並且能夠在鉤上事件時,處理一些自己特定的事件。

Hook 的選擇點:
靜態變數和單例,因為一旦建立物件,它們不容易變化,非常容易定位。
Hook 過程:
尋找 Hook 點,原則是靜態變數或者單例物件,儘量 Hook public 的物件和方法。
選擇合適的代理方式,如果是介面可以用動態代理。
偷樑換柱——用代理物件替換原始物件。
Android 的 API 版本比較多,方法和類可能不一樣,所以要做好 API 的相容工作

應用

  • Hook指定應用注入廣告
  • 修復bug
  • App登入劫持

    登入介面上面的使用者資訊都儲存在EditText控制元件上,然後通過使用者手動點選“登入”按鈕才會將上面的資訊傳送至伺服器端去驗證賬號與密碼是否正確。這樣就很簡單了,黑客們只需要找到開發者在使用EditText控制元件的getText方法後進行網路驗證的方法,Hook該方法,就能劫持到使用者的賬戶與密碼了

hook練習


AOP(AspectJ)

正如面向物件程式設計是對常見問題的模組化一樣,面向切面程式設計是對橫向的同一問題進行模組化,比如在某個包下的所有類中的某一類方法中都需要解決一個相似的問題,可以通過AOP的程式設計方式對此進行模組化封裝,統一解決
關於AOP的具體解釋,可以參照維基百科。而AspectJ就是面向切面程式設計在Java中的一種具體實現。

Join point:程式中執行程式碼插入的點,例如方法呼叫時或者方法執行時。

AOP程式設計的具體使用場景
日誌記錄
持久化
行為監測
資料驗證
快取
...

比如埋點,記錄方法執行的時長。可以定義註解。aop可以過濾所有被"這個註解"標記的方法和構造器。然後可以可以根據他提供的方法(註解),講我們想要埋點的日誌插入進去。

反射和註解
反射:對於任何一個物件,都能夠呼叫它的任何一個方法和屬性,包括私有的。這種動態獲取的方法就叫反射。
註解:降低專案的耦合度;自動完成一些規律性的程式碼;自動生成java程式碼,減輕開發者的工作量。
@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) {
          }
        }
      }
  

}

註解和反射效率問題
反射先new類class,然後在從類裡面new物件。Class.getMethod(...)還要查詢所有的方法。
而註解編譯期間就完成了註解的反射工作, jvm只是讀取。


反射機制

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;
    }
    
}