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的纯净,又能够完成需求,还很是简单,何乐而不为呢?

那么看完这篇文章,你们能够审视下本身的项目中还有哪些地方能够用动态代理来设计呢?有兴趣的同窗能够留言给我分享。

相关文章
相关标签/搜索