代理、反射、註解、hook

代理

經過代理對象訪問目標對象.這樣作的好處是:能夠在目標對象實現的基礎上,擴展目標對象的功能。
代理對象攔截真實對象的方法調用,在真實對象調用前/後實現本身的邏輯調用
這裏使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,若是需改修改,能夠經過代理的方式來擴展該方法。java

動態代理的用途與裝飾模式很類似,就是爲了對某個對象進行加強。全部使用裝飾者模式的案例均可以使用動態代理來替換。
git

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

Hook 的選擇點:
靜態變量和單例,由於一旦建立對象,它們不容易變化,很是容易定位。
Hook 過程:
尋找 Hook 點,原則是靜態變量或者單例對象,儘可能 Hook public 的對象和方法。
選擇合適的代理方式,若是是接口能夠用動態代理。
偷樑換柱——用代理對象替換原始對象。
Android 的 API 版本比較多,方法和類可能不同,因此要作好 API 的兼容工做編程

應用緩存

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

    登陸界面上面的用戶信息都存儲在EditText控件上,而後經過用戶手動點擊「登陸」按鈕纔會將上面的信息發送至服務器端去驗證帳號與密碼是否正確。這樣就很簡單了,黑客們只須要找到開發者在使用EditText控件的getText方法後進行網絡驗證的方法,Hook該方法,就能劫持到用戶的帳戶與密碼了網絡

hook練習jvm


AOP(AspectJ)

正如面向對象編程是對常見問題的模塊化同樣,面向切面編程是對橫向的同一問題進行模塊化,好比在某個包下的全部類中的某一類方法中都須要解決一個類似的問題,能夠經過AOP的編程方式對此進行模塊化封裝,統一解決
關於AOP的具體解釋,能夠參照維基百科。而AspectJ就是面向切面編程在Java中的一種具體實現。ide

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;
    }
    
}
相關文章
相關標籤/搜索