Android 动态代理浅叙

来源:互联网 发布:音轨合成软件 编辑:程序博客网 时间:2024/06/07 01:22

  前两篇讲了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:5query 获取到二次计算后数据: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:5query 获取到二次计算后数据: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编译机制,我理解得还比较浅显,所以不敢深入讲它的一个原理,只能浅面讲它的一个基本运用。
个人观点,难免有不正确错误的地方,欢迎大家不吝指出。如果需要转载,请注明出处,谢谢。
源码下载

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 六角螺母拧圆了怎么办 饺子冻在盘子上怎么办 煮熟的饺子坨了怎么办 六角螺丝拧圆了怎么办 长杆螺丝滑丝了怎么办 起泡器不起泡了怎么办 不喂母乳涨奶怎么办 螺丝和螺母跟转怎么办 六棱螺丝滑丝了怎么办 苹果6螺丝滑牙了怎么办 外六角螺丝圆了怎么办 小螺丝拧花了怎么办 老人退伍证丢了怎么办 狗狗又拉又吐怎么办 孕妇吃了马兰头怎么办 怀孕吃了马兰头怎么办 吃了茭白和豆腐怎么办 电气焊加工怎么办环评 手上皮肤干燥起皮怎么办 脸上起皮怎么办还痒痒 店铺4周被释放了怎么办 炫舞账号忘了怎么办 椎基底供血不足怎么办? 脑动脉供血不足怎么办 颈椎引起的脑供血不足怎么办 军人保障卡怎么办假的 正常形态精子率低怎么办 前向运动精子21怎么办 前向运动精子22%怎么办 前向运动精子19%怎么办 前向运动精子为0怎么办 被蝎子草扎了怎么办 二年级孩子成绩差怎么办 长治医保卡丢了怎么办 农商银行倒闭钱怎么办 2相电变3相电怎么办 电机六根线乱了怎么办 三相电零线带电怎么办 孕30周胎盘偏厚怎么办 偏侧咀嚼大小脸怎么办 咀嚼导致的脸歪怎么办