动态切换的动态代理

码途聆云者
• 阅读 1423

名字看着有点绕, 但目的其实很简单明确: 就是想实现动态代理的对象实例, 在运行时也能够切换.
先理解前提条件和程序上下文, 譬如有如下接口:

public interface Responder {
    void onMethod1(String s);
    int onMethod2();
    void onMethod3();
}

我们将接口的一个实例Responder r1传入了一个别的类p1 = new Presenter(r1)(或者外部SDK), 在运行时我们生成了不同的Responder实例r2, 现在希望r2能够替换r1, 但对于Presenter类是无法感知, 不用关心的. 显然我们的程序上下文能够实现对于Responder实例的控制(创建/传递), 但现在问题是Presenter类仅有构造参数对Responder的传入, 没有setResponder(Responder r)这样的方法(如果存在setResponder这样的方法, 但就没这坨事了:). 能不能再创建一个Presenter实例p2再传入r2呢? 如果程序上下文允许的话也没这坨事了.

所以条件是这样: 接口的不同实例需要传入一个对象, 但这个对象持有的实例却无法更改, 同时这个对象也无法再次创建.

说这么多不就是要用代理模式吗? 不错, 代理模式正是可以解决这类问题的. 表述这么累赘是想关注问题的场景, 而不是为了生搬硬套模式.

于是一个简单的代理类出来了:

public class ResponderWrapper implements Responder {
    private final Responder impl;
    public ResponderWrapper(Responder r) {
        impl = r;
    }
    @Override
    void onMethod1(String s) {
        impl.onMethod1(s);
    }
    @Override
    int onMethod2() {
        return impl.onMethod2();
    }
    @Override
    void onMethod3() {
        impl.onMethod3();
    }
}

因为还要动态的改变代理对象所以添加一个set方法:

    void setResponder(Responder r) {
        impl = r;
    }

那么传入Presenter对象的实例就不再是r1了, 而是

wrapper = new ResponderWrapper(r1);
p1 = new Presenter(wrapper);

这时创建了新的Responder实例r2, 我们只需要

wrapper.setResponder(r2);

就能够达到我们的目的了! p1还是p1, p1持有的实例还是同一个实例, 在切换前p1调的是r1的实现, 切换后自然就调用了r2的实现.
这种代理就是非常常见的静态代理, 仅就功能实现来说这已经完全OK了, 没有任何问题了. 是不是非得用动态代理? 并不是!

那动态代理是干吗的? 为了适应变化, 什么的变化? 接口的变化! 如果接口Responder新增一个方法, ResponderWrapper再增加同样一个接口; 如果修改Responder一个方法的参数, ResponderWrapper再接着修改并调用接口实例的新方法, 如此类推, 也没任何问题. 但接口的方法一旦变的很多, 接口的实现类一旦变的很多, 就需要做大量繁琐重复的工作, 那么动态代理就能够解决这种重复繁琐的工作.
以动态代理的形式写一个ResponderWrapper非常简单:

public final class ResponderWrapper {
    public static Responder wrap(final Responder responder) {
        return (Responder) Proxy.newProxyInstance(Responder.class.getClassLoader(),
                Responder.class.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(responder, args);
                    }
                });
    }
}

但是这样写无法满足动态切换的需求, 所以我们的最终目的这才出来了: 以动态代理形式创建的代理实例能够动态切换持有的对象实例
但一旦ResponderWrapper.wrap传入r1那么匿名对象持有的Responder对象就只能一直是r1, 所以希望method.invoke(responder, args)这里的responder能够动态切换, 这种"动态"能力一般都是以接口的形式实现, 于是有:

public final class ResponderWrapper {
    public interface Provider {
        Responder get();
    }

    public static Responder wrap(final Provider provider) {
        return (Responder) Proxy.newProxyInstance(Responder.class.getClassLoader(),
                Responder.class.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(provider.get(), args);
                    }
                });
    }

程序上下文实现ResponderWrapper.Provider接口, 当接口方法被调用时返回的实例是当前的Responder, 不用关心什么时候切换:

mResonder = r1;
wrapper = ResponderWrapper.wrap(new ResponderWrapper.Provider() {
    @Override
    public ResponderWrapper.Responder get() {
        return mResponder;
    }
});
p1 = new Presenter(wrapper);
...
mResonder = r2;

如果觉得接口太重, 其实这种形式也完全可以不用接口的方式实现, 因为我们最终需要的其实是一个Responder实例, 在接口方法被调用的时候能够调用这个实例的对应的方法而已, 所以可以写成这样:

public final class ResponderWrapper {
    public static final class Holder {
        public Responder responder;
    }

    public static Responder wrap(final Holder holder) {
        return (Responder) Proxy.newProxyInstance(Responder.class.getClassLoader(),
                Responder.class.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(holder.responder, args);
                    }
                });
    }
}

程序上下文持有ResponderWrapper.Holder的实例, 再在需要的时候设置不同的Resonder实例:

mHolder = new ResponderWrapper.Holder(r1);
wrapper = ResponderWrapper.wrap(holder)
p1 = new Presenter(wrapper);
...
mHolder.responder = r2

如果用范型抽象所有接口类, 就可以写的更通用一点:

public final class ResponderWrapper {
    public static final class Holder<T> {
        public T responder;
    }

    @SuppressWarnings("unchecked")
    public static <T> T wrap(final Holder<T> holder) {
        T r = holder.responder;
        return (T) Proxy.newProxyInstance(r.getClass().getClassLoader(),
                r.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(holder.responder, args);
                    }
                });
    }
}

这里临时利用holder.responder来获取ClassLoader和Class<?>[], 也完全可以将Class对象传入:

public final class ResponderWrapper {
    public static final class Holder<T> {
        public T responder;
    }

    @SuppressWarnings("unchecked")
    public static <T> T wrap(final Holder<T> holder, final Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(holder.responder, args);
                    }
                });
    }
}

这就是我们所谓的动态切换的动态代理了.

点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
java的动态代理
1\.什么是动态代理代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色。Java的动态代理比代理的思想更前进了一步,它可以动态地创建并代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相
Wesley13 Wesley13
4年前
JDK动态代理和Cglib的动态代理
最简单的是静态代理方法,即代理模式,这里就不多啰嗦了。。重点说一下JDK的动态代理和Cglib的动态代理吧先说JDK的,需要被代理的类需要有接口,否则无法实现package proxy.dynamic;public interface IBook {void add();}实现接口
Wesley13 Wesley13
4年前
CGLIB介绍与原理(通过继承的动态代理)
一、什么是CGLIB?CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。二、CGLIB原理CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的
Easter79 Easter79
4年前
Spring的两种代理JDK和CGLIB的区别浅谈
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Easter79 Easter79
4年前
Spring的两种动态代理:Jdk和Cglib 的区别和实现
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Wesley13 Wesley13
4年前
JDK动态代理的简单实现
1\.先理一下动态代理实现的思路:    实现功能:自己定义一个类Proxy,通过Proxy的静态方法newProxyInstance(Class<Tintface,InvocationHandlerh)返回代理对象, intface:被代理类的接口对象, h:InvocationHandler的实例对象    1).声明一段动
Wesley13 Wesley13
4年前
Java动态代理机制解析
动态代理是指在运行时动态生成代理类。不需要我们像静态代理那个去手动写一个个的代理类。生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等。这里主要说一下Java动态代理的实现。Java动态代理InvocationHandler接口Java动态代理中,每一个
Wesley13 Wesley13
4年前
Java提高班(六)反射和动态代理(JDK Proxy和Cglib)
反射和动态代理放有一定的相关性,但单纯的说动态代理是由反射机制实现的,其实是不够全面不准确的,动态代理是一种功能行为,而它的实现方法有很多。要怎么理解以上这句话,请看下文。一、反射反射机制是Java语言提供的一种基础功能,赋予程序在运行时<strong自省</strong(introspect,官方用语)的能力。通过反射我们可以直接
Wesley13 Wesley13
4年前
JDK动态代理
一,什么是代理?代理:本来是自己应该做的事,却请了别人来做,被请的人就是代理对象举例:春节回家买票找人代买,黄牛就是代理对象二,什么是动态代理?代理的对象是变动的,在程序运行过程中产生的.而在程序运行过程中产生对象,这个对象是不固定的,那么可以通过反射来实现,所以动态代理是基于反射实现的.
Wesley13 Wesley13
4年前
Java动态代理
jdk动态代理实现原理:利用字节码技术,生成新的class文件,来达到动态代理效果。新的class文件是怎么组织的?由于代理目标是接口,则通过实现接口和继续代理类来完成。看看下面的例子更容易明白。demo接口publicinterfacePeoPleInterface{