Android程序員必會技能---運行時動態生成類---之動態代理

談到java中的動態生成一個類,主要分爲兩種方法,一種就是動態代理,另一種就是asm。今天咱們就來把對第一種方法 也就是動態代理生成類,這個流程搞清楚吃透。java

要搞清楚動態代理,首先要弄明白爲何須要動態代理?靜態代理不夠用嗎?

首先考慮一個場景,團隊中git提交的時候是否是都要通過leader review代碼 贊成之後 纔會真正的上傳到master分支上?android

那針對此場景,咱們能夠寫以下代碼:git

首先定義一下團隊成員接口bash

/**
 * Created by admin on 2018/12/8.
 */
public interface TeamMember {
    public void reviewCode();

}

複製代碼

而後定義一個A小組的成員app

/**
 * Created by admin on 2018/12/8.
 */
public class TeamAMember implements TeamMember {
    public TeamAMember(String name) {
        this.name = name;
    }

    String name;

    @Override
    public void reviewCode() {
        System.out.println("A小組成員" + name + "代碼review過了");
    }
}


複製代碼

而後定義一下咱們的代理類,也就是leader來代理review你們的代碼框架

/**
 * Created by admin on 2018/12/8.
 */
public class TeamLeaderProxy implements TeamMember {
    public TeamLeaderProxy(TeamMember teamMember) {
        //只有a組的成員代碼 我才review代碼
        if (teamMember instanceof TeamAMember) {
            this.teamMember = teamMember;
        }
    }

    TeamMember teamMember;


    @Override
    public void reviewCode() {
        if (teamMember != null) {
            teamMember.reviewCode();
        }
    }
}

複製代碼

最後調用以下:ide

public class Main {

    public static void main(String[] args) {
        TeamAMember teamAMember=new TeamAMember("wuyue");
        TeamLeaderProxy teamLeaderProxy=new TeamLeaderProxy(teamAMember);
        teamLeaderProxy.reviewCode();
    }


}
複製代碼

輸出結果也很簡單:測試

那這樣作有什麼好處呢?比方說咱們發現最近a小組成員的代碼好像註釋都比較少,那麼咱們就能夠在代理類裏面修改咱們的代碼ui

/**
 * Created by admin on 2018/12/8.
 */
public class TeamLeaderProxy implements TeamMember {
    public TeamLeaderProxy(TeamMember teamMember) {
        //只有a組的成員代碼 我才review代碼
        if (teamMember instanceof TeamAMember) {
            this.teamMember = teamMember;
        }
    }

    TeamMember teamMember;


    @Override
    public void reviewCode() {
        if (teamMember != null) {
            System.out.println("註釋代碼太少了 注意多寫註釋");
            teamMember.reviewCode();
        }
    }
}

複製代碼

其實這就是一個aop的思想,在代理的過程當中加入一些操做,能夠在代理的操做以前增長操做,也能夠在代理的操做以後增長操做this

這裏是靜態代理,靜態代理就是說咱們這個代理類是咱們以前定義好的,由咱們寫的java代碼而後編譯好的。這裏有什麼缺陷呢?

想象一下,咱們除了review組員的代碼,做爲一個leader咱們還要幹不少其餘事情,好比查看組員內每週的代碼提交行數, 查看組員內最近上下班時間,查看組員內完成了todo list上面的哪些事情,等等。

若是某一天我但願給本身的這些事情都加入一個時間戳,那不是須要到這些方法裏面依次修改?而後修改完之後再次編譯?

若是這個team還有其餘leader呢?那要修改的地方就更多了!

太麻煩了!!

所謂動態代理就是讓咱們 動態的去生成一個代理類,這樣咱們就不須要依次的修改這些方法了!並且能夠根據須要, 在不一樣的場景下 生成不一樣的代理!是否是簡單方便不少?

假設咱們如今有個需求是,每次leader review的時間都要打印出來給CTO看,CTO想看看leader們的工做狀態,

那麼咱們針對此需求能夠用動態代理來實現:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by admin on 2018/12/8.
 */
public class TeamMemberInvocation<T> implements InvocationHandler {

    T target;

    public TeamMemberInvocation(T target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("準備動態代理執行" + method.getName() + "方法");
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
        System.out.println("執行的時間爲" + df.format(new Date()));
        Object result = method.invoke(target, args);
        return null;
    }
}

複製代碼
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {

    public static void main(String[] args) {
        TeamAMember teamAMember = new TeamAMember("wuyue");

        InvocationHandler teamMemberHandler = new TeamMemberInvocation<TeamMember>(teamAMember);

        TeamMember dynamicProxy = (TeamMember) Proxy.newProxyInstance(TeamMember.class.getClassLoader(), new Class<?>[]{TeamMember.class}, teamMemberHandler);

        dynamicProxy.reviewCode();


    }


}

複製代碼

而後看一下咱們的輸出結果:

誒?你看獲得了咱們想要的結果,可是咱們在代碼裏面並無寫一個實際的代理類啊?這個代理類到底藏到哪裏去了?

動態代理是如何執行的?

咱們首先想知道代理的類到底藏在哪 長什麼樣是吧? 因此能夠增長一行代碼

System.out.println("dynamicProxy 實際的類=="+dynamicProxy.getClass().getName());

複製代碼

而後查看結果爲: dynamicProxy 實際的類==com.sun.proxy.$Proxy0

查看咱們的代碼 重要部分

繼續跟:

而後看ProxyClassFactory代碼

真相大白了。可是由於這個類是動態運行時候生成的,因此咱們想看他的字節碼反編譯之後的代碼 就得拿到這個class文件,可是顯然咱們是拿不到這個文件的。可是由於找源碼找到這裏

知道這個動態類是怎麼生成的,因此咱們能夠寫一段測試代碼,看看能不能生成一下這個動態類, 而後把這個類的字節碼class文件 輸出到咱們的硬盤裏,而後反編譯不就能看到這個類的實際內容了?

public static void writeClassToDisk() {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{TeamMember.class});
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("D:\\wuyue\\out\\production\\$Proxy0.class");
            fos.write(classFile);
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
複製代碼

而後運行咱們的代碼,果真,咱們終於生成了這個動態的類 字節碼文件:

而後固然就是反編譯看看他了:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements TeamMember {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void reviewCode() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("TeamMember").getMethod("reviewCode", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

複製代碼

知道了動態類是怎麼生成的了,那最後一個問題,動態代理類的方法執行爲何最終執行的是咱們InvocationHandler的invoke方法?

先看h是什麼?

真相大白 原來h 就是咱們的InvocationHandler實例啊!

而後再看看m3

到這裏也就真相大白了,其實不難,總結一下:

動態生成的類平時就放在內存中,而後實際調用的時候經過反射來call他的構造方法,而後就能夠獲得這個代理類的對象了。 注意這個代理類的對象還持有咱們invocationhandler的對象噢,最終call代理類對象方法的時候其實都是call的這個invocationhandler中介類的invoke方法

用代理來在android上作點有趣的事吧

需求:想幹一件壞事,無論同事start 哪一個activity 最終都會 start到個人activity上。

要完成這件事首先你要大概瞭解activity的啓動流程。這裏由於篇幅的緣由 不做詳細介紹 只大概提一下。

實際上activity start的過程主要是根據下面代碼而來:

ContextImpl的這個方法:

@Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
複製代碼

能夠看出來這個方法也是由mMainThread.getInstrumentation()來執行最終的方法。 因此這裏咱們只要想辦法讓getInstrumentation返回咱們本身的Instrumentation 不就能夠了嗎?這裏的思路注意和靜態代理實際上是差很少的

因此咱們能夠先構造一個假的Instrumentation

package com.longfor.dynamicproxyexample;

import android.app.Activity;
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.IBinder;
import android.util.Log;

public class FakeInstrumentation extends Instrumentation {

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //這裏爲了簡單 判斷一下 若是是同事想啓動的activity 那麼就轉到我本身的activity上來
        if ("com.longfor.dynamicproxyexample.TestOneActivity".equals(className)) {
            className = "com.longfor.dynamicproxyexample.TestTwoActivity";
        }
        return super.newActivity(cl, className, intent);
    }

  
}


複製代碼

剩下的就是想辦法hook到activitythread對象和更換裏面的Instrumentation對象啦。

package com.longfor.dynamicproxyexample;

import android.app.Application;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyApplication extends Application {
    static Object activityThreadInstance;

    @Override
    public void onCreate() {
        try {
            //這邊反射的代碼要注意理解,class.forName的返回的並非一個對象 而是一個類
            Class<?> activityThread = Class.forName("android.app.ActivityThread");
            //而currentActivityThread這個方法是一個靜態方法,由於call 一個類的靜態方法並不須要用對象來call
            //直接用類就能夠call ,因此這裏咱們用class.forName獲得的類就能夠直接call這個靜態方法
            //從而不須要像日常的反射代碼同樣要找到一個對象才能反射call這個對象的方法
            Method currentActivityThread = activityThread.getDeclaredMethod("currentActivityThread");
            activityThreadInstance = currentActivityThread.invoke(null);

            //拿到sCurrentActivityThread實例之後就簡單多了 就直接替換這裏面的mInstrumentation 變量便可
            Field field_instrumentation = activityThreadInstance.getClass()
                    .getDeclaredField("mInstrumentation");
            field_instrumentation.setAccessible(true);
            FakeInstrumentation fakeInstrumentation = new FakeInstrumentation();
            field_instrumentation.set(activityThreadInstance, fakeInstrumentation);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }


        super.onCreate();

    }
}

複製代碼

看下效果:

而後你就會發現無論你啓動哪一個activity最終都會跳轉到個人testtwo這個頁面上了。 實際上大部分插件框架 hook activity就是根據此思路!

那有人就要問了,你這是靜態代理啊,動態代理在android裏面咋用呢?其實動態代理要用起來就一個最基本的要素 就是你想代理的方法和類 必須是派生自interface的。 這就對咱們的程序設計有更高的要求。

比方說,如今有個需求是 須要你作一個日誌的sdk,而後此日誌sdk 要提供給 不一樣的6個團隊使用,而後 用了一段時間之後,各方面反饋說 都要在本身的地方輸出特殊格式的時間戳,那怎麼作呢?有人說能夠升級sdk提供新的方法啊, 根據不一樣的參數,sdk裏面輸出不同的時間戳啊。 那我就要問了,若是之後有n個團隊咋辦?不可能在sdk裏寫n個if else邏輯吧

其實這裏就能夠用到動態代理了,徹底能夠把輸出日誌的方法寫到一個interface裏面,而後誰要用時間戳之類的特性, 就本身動態代理一下本身加,這樣既能保證sdk的純淨,又能夠完成需求,還很是簡單,何樂而不爲呢?

那麼看完這篇文章,你們能夠審視下本身的項目中還有哪些地方能夠用動態代理來設計呢?有興趣的同窗能夠留言給我分享。

相關文章
相關標籤/搜索