Android程序员必会技能---运行时动态生成类---之动态代理

6,386 阅读8分钟

谈到java中的动态生成一个类,主要分为两种方法,一种就是动态代理,另外一种就是asm。今天我们就来把对第一种方法 也就是动态代理生成类,这个流程搞清楚吃透。

要搞清楚动态代理,首先要弄明白为什么需要动态代理?静态代理不够用吗?

首先考虑一个场景,团队中git提交的时候是不是都要经过leader review代码 同意以后 才会真正的上传到master分支上?

那针对此场景,我们可以写如下代码:

首先定义一下团队成员接口

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

}

然后定义一个A小组的成员

/**
 * 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();
        }
    }
}

最后调用如下:

public class Main {

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


}

输出结果也很简单:

那这样做有什么好处呢?比方说我们发现最近a小组成员的代码好像注释都比较少,那么我们就可以在代理类里面修改我们的代码

/**
 * 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的思想,在代理的过程中加入一些操作,可以在代理的操作之前增加操作,也可以在代理的操作之后增加操作

这里是静态代理,静态代理就是说我们这个代理类是我们之前定义好的,由我们写的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的纯净,又可以完成需求,还非常简单,何乐而不为呢?

那么看完这篇文章,大家可以审视下自己的项目中还有哪些地方可以用动态代理来设计呢?有兴趣的同学可以留言给我分享。