Android 动态代理浅叙

  前两篇讲了Android和Js的交互、NDK开发,今天准备写一篇关于动态代理的博客。记得在上家公司项目有个模块用到了动态代理,并且在它基础上进行了扩展运用,自己接触动态代理还是在最早学Java的时候,然后结果大家懂的…
  虽然动态代理用起来比较简单,并不复杂,但我还是想写一篇关于它的文章,不为其它的,只为对自己一个交待^-^
  在项目开发里,有时候可能会有这种情况,因为某些原因或者需求变动,需要在不改变某个类的某个方法的代码前提下,添加一些额外的逻辑代码进去。这种情况怎么实现?可能第一个想到的——代理,就是今天准备说的代理模式。
  比如,现在要添加额外逻辑代码的类(也就是被代理类)的代码,(ps:为了方便演示,这里是创建的Java工程):

public class MyClass {

    //不可以更改coumpute(int a, int b)方法内部的代码逻辑
    public void coumpute(int a, int b){

        System.out.println("正在执行计算逻辑,计算参数,a:" + a + ",b:" + b);
    }
}

  这里为了简单起见,只是在compute方法里打印了一条日志,现在要求在不改变compute方法任何代码的前提下,在计算逻辑执行之前,执行一段其它代码。
  使用动态代理实现,需要满足两个条件,一:代理类和被代理类必须实现同一个接口;二:需要使用到InvocationHandler接口,添加的额外逻辑,主要就是在InvocationHandler实现类的invoke方法中进行实现。
  那我们进行第一步,定义一个接口,定义被代理类中的方法

public interface IMyInterface {

    void compute(int a, int b);
}

  很简单,就是定义了一个compute方法,然后让MyClass实现定义的接口。第二步,新建一个DynamicProxyHandler实现InvocationHandler,在其中实现添加额外逻辑代码。具体实现代码:

public class DynamicProxyHandler implements InvocationHandler{
    //被代理对象
    private Object beProxy;
    public DynamicProxyHandler(Object beProxy){
        this.beProxy = beProxy;
    }
    //通过Proxy类,获取代理对象
    public Object getProxy(){
        return Proxy.newProxyInstance(
                //代理对象和被代理对象应该在同一类加载器
                this.beProxy.getClass().getClassLoader(),
                //获取被代理对象实现的接口(代理类和被代理类必须实现同一接口)
                this.beProxy.getClass().getInterfaces(),
                this
        );
    }
    //不需要手动调用,当代理对象某个方法被调用时,自动调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行添加的额外代码逻辑");
        Object result = method.invoke(this.beProxy, args);
        return result;
    }

  先通过构造传入被代理对象,然后通过Proxy.newProxyInstance(),获取到代理对象。代理对象和被代理对象必须实现同一接口,代理对象和被代理对象必须实现同一接口,重要的事情说三遍。然后我们要实现的添加,主要就在invoke方法中,动态代理所有的处理全部交给InvocationHandler,InvocationHandler所有被代理方法的处理,全部在invoke方法中。这里,我们也只是简单得在compute方法执行之前,打印了一条日志。
  好,看代码应该已经实现了动态代理,我们新建一个MainCls类,(ps:因为我compute方法里有中文,所以在模块的build.gradle文件里,添加了tasks.withType(JavaCompile) {options.encoding = “UTF-8”},避免控制台输出乱码)

public class MainCls {
    public static void main(String[] args){
        //获取代理对象
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(new MyClass());
        IMyInterface proxy = (IMyInterface) dynamicProxyHandler.getProxy();
        //调用compute方法
        proxy.compute(3, 5);
    }
}

  运行测试一下结果:

执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5

  输出了这两条log,说明动态代理成功。看到这里,可能有朋友会问,这只是一个方法的代理,如果有多个方法需要代理?
  刚刚说过InvocationHandler所有被代理方法的处理,全部是交给了invoke方法处理,那我们直接根据invoke方法的method参数,进行方法名区分,就可以了。比如,MyCls多添加一个query()方法,要求获取到query()方法值后,进行一次额外运算之后,将最后运算结果返回。实现也比较简单,MyCls中添加了query()方法

//不可以更改query()方法内部的代码逻辑,查询到的值,进行一次额外运算逻辑,将最后运算结果返回
    @Override
    public int query() {
        return 10;
    }

  IMyInterface添加query()

void compute(int a, int b);
    int query();

DynamicProxyHandler更改invoke方法实现逻辑:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        String methodName = method.getName();
        if("compute".equals(methodName)){
            //在执行compute方法计算逻辑之前,添加一段额外代码逻辑
            System.out.println("compute 执行添加的额外代码逻辑");//添加的额外代码逻辑
            result = method.invoke(this.beProxy, args);
        } else if("query".equals(methodName)){
            //获取query方法查询结果
            result = method.invoke(this.beProxy, args);
            //进行额外计算,然后将结果返回
            if(result instanceof Integer){
                int r = (Integer)result;
                r*=1000;
                System.out.println("query 获取到二次计算后数据:" + r);
                return r;
            }
        }
        return result;
    }

然后MainCls添加query()方法的调用,运用一下测试结果:

compute 执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
query 获取到二次计算后数据:10000
查询到最新数据:10000

说明compute(…)和query()方法都达到要求,全部代理成功。动态代理,大致就是这样一个实现过程。不过每次实现的时候,都需要手动传入被代理的对象实例,这样看起来,好像low了一点。直接使用反射,可不可以呢?我们试一下

//使用反射
        return Proxy.newProxyInstance(
                Class.forName("com.example.MyClass").newInstance().getClass().getClassLoader(),
                new Class<?>[]{Class.forName("com.example.IMyInterface")},
                this);

运行一下,发现结果一致,说明成功。这样调用好像还显得代码有些多,干脆直接这样调用:

IMyInterface proxy = (IMyInterface)Proxy.newProxyInstance(                MainCls.class.getClassLoader(),
new Class<?>[]{Class.forName("com.example.IMyInterface")},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            Object result = null;
                            String methodName = method.getName();
                            if("compute".equals(methodName)){
                                //在执行compute方法计算逻辑之前,添加一段额外代码逻辑
                                System.out.println("compute 执行添加的额外代码逻辑");//添加的额外代码逻辑
                                result = method.invoke(//被代理类实例
                                        Class.forName("com.example.MyClass").newInstance(), args);
                            } else if("query".equals(methodName)){
                                //获取query方法查询结果
                                result = method.invoke(Class.forName("com.example.MyClass").newInstance(), args);
                                //进行额外计算,然后将结果返回
                                if(result instanceof Integer){
                                    int r = (Integer)result;
                                    r*=1000;
                                    System.out.println("query 获取到二次计算后数据:" + r);
                                    return r;
                                }
                            }
                            return result;
                        }
                    });

运行:

compute 执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
query 获取到二次计算后数据:10000
查询到最新数据:10000

输出结果一致,说明代理成功。(ps:这种调用比较粗暴,不太推荐)
代理模式分动态代理和静态代理,这里我们主要讲的动态代理,如果用静态代理,应该怎么实现?
新建StaticProxy类,实现IMyInterface

public class StaticProxyCls implements IMyInterface{
    private IMyInterface beProxy;
    public StaticProxyCls(IMyInterface beProxy){
        this.beProxy = beProxy;
    }
    @Override
    public void compute(int a, int b) {
        System.out.println("静态代理 执行添加的额外代码逻辑");//添加的额外代码逻辑
        beProxy.compute(a,b);
    }
    @Override
    public int query() {
        int n = beProxy.query();
        return n*1000;
    }
}

在MainCls中调用,运行:

静态代理 执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
静态代理 查询到最新数据:10000

说明代理成功。静态代理,相当于组合实现,小量的使用还好,如果大量的使用,缺点就非常明显。比如现在有1000个类需要代理,那就要需要创建1000个来实现对应接口的代理类?!!!
动态代理的原理,涉及到JVM编译机制,我理解得还比较浅显,所以不敢深入讲它的一个原理,只能浅面讲它的一个基本运用。
个人观点,难免有不正确错误的地方,欢迎大家不吝指出。如果需要转载,请注明出处,谢谢。
源码下载

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值