Java中的代理模式

2019-02-11  本文已影响0人  几行代码

因为Retrofit剖析源码的时候会用到ava中的代理模式,所以这篇就先回忆一下代理设计模式。
代理模式分为两种:
代理模式解释:为其他对象提供一种代理,用以控制对这个对象的访问。(直白一点就是代替别人去做一些我们想要做的事情)
1.静态代理(抽象类)


image.png

代码实现:

/**
 * 被目标对象和代理对象同时继承的抽象类
 */
public abstract class AbstractObject {
    protected abstract void operation(); // 经营预算
}

被代理对象:

/**
 * 目标对象 (被代理对象)
 */
public class RealObject extends AbstractObject{

    private static final String TAG = RealObject.class.getSimpleName();

    @Override
    public void operation() {
        Log.e(TAG,"do operation......");
    }
}

代理对象:

/**
 * 代理类
 */
public class ProxyObject extends AbstractObject {

    private static final String TAG = ProxyObject.class.getSimpleName();
    // 对目标类的引用
    private RealObject mRealObject;

    public ProxyObject(RealObject mRealObject) {
        this.mRealObject = mRealObject;
    }

    @Override
    public void operation() {
        Log.e(TAG,"do something before real operation......");
        if (mRealObject == null) {
            mRealObject = new RealObject();
        }
        mRealObject.operation();
        Log.e(TAG,"do something after real operation......");
    }
}

测试:

public static void main(String[] args) {
        ProxyObject proxyObject = new ProxyObject(new RealObject());
        proxyObject.operation();
    }

do something before real operation......
do operation......
do something after real operation......

2.动态代理(JDK实现/CJLib继承)

JDK动态代理:Java内部的反射机制来实现的,这个机制在生成类的时候比较高效。(必须要针对接口进行代理)

代码示例:
被目标对象和代理对象都要实现的接口

/**
 * 动态代理
 * 被目标对象和代理对象都要实现的接口
 */
public interface InterfaceObject {
    void shopping();
}

被代理类 (目标类):

/**
 * 动态代理
 * 被代理类 (目标类)
 */
public class BeRepresent implements InterfaceObject {
    private static final String TAG = BeRepresent.class.getSimpleName();
    @Override
    public void shopping() {
        System.out.println("买点东西回来");
    }
}

代理类是由JDK动态创建的:

/**
 * 动态代理
 * 代理类:每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现
 * 当使用者调用了代理对象,所实现的代理对象的接口时,这个调用的信息就会传递到InvocationHandler
 * 中的invoke方法中
 */
public class ProxyF implements InvocationHandler {

    private static final String TAG = ProxyF.class.getSimpleName();
    private Object target; // 要代理的真实对象

    public ProxyF(Object target) {
        this.target = target;
    }

    /**
     * 相当于一个拦截的方法
     * @param proxy   代理对象
     * @param method  代理方法
     * @param objects 代理方法中的参数
     * @return 返回值会返回给使用者
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
        System.out.println("代理对象:" + proxy.getClass().getName());
        System.out.println("代理方法中的参数:" + (objects != null && objects.length > 0 ? objects[0] : null));
        System.out.println("代理方法:" + method);
        System.out.println(" before ");
        method.invoke(target,objects);
        System.out.println(" after ");
    }
}

测试:

public static void main(String[] args) {
        InterfaceObject mBeRepresent = new BeRepresent();
        ProxyF proxy = new ProxyF(mBeRepresent);
        // java.lang.reflect.Proxy.newProxyInstance(....)方法获得真实对象的代理对象 (反射)
        InterfaceObject mInterfaceObject = (InterfaceObject) Proxy.newProxyInstance(mBeRepresent.getClass().getClassLoader()
                , mBeRepresent.getClass().getInterfaces(), proxy);
        // 通过代理对象调用真实对象相关接口中实现的方法,这个时候就会跳转到这个代理对象所关联的InvocationHandler
        // 的invoke方法中
        mInterfaceObject.shopping();
        // 获得真实对象的代理对象所对应的class对象的名称,用字符串表示
        System.out.println(mInterfaceObject.getClass().getName());
}

结果:

代理对象:com.sun.proxy.$Proxy0
代理方法中的参数:null
代理方法:public abstract void dynamicproxy.InterfaceObject.shopping()
 before 
买点东西回来
 after 
com.sun.proxy.$Proxy0

注意:被代理对象的类必须自己定义时就实现接口,从该类的祖辈类上继承的接口是无效的。
总结:
JDK实现原理:

1.拿到被代理对象的引用,然后获取它实现的接口
2.JDK重新生成一个类,同时实现获取到被代理对象所实现的接口
3.在代理类的静态构造块中,代理类通过反射获取了被代理类的详细信息
4.重新生成一个class字节码
5.然后编译
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

还有一种是CGlib实现的动态代理:
cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。

静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;
JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;
CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;

最后,抛出两个问题:

1.retrofit为什么要使用动态代理 ?
答案:
因为1个静态代理只服务1种类型的目标对象,若要服务多类型的目标对象,则需要为每种目标对象都实现一个静态代理对象 ,在目标对象较多的情况下,若采用静态代理,则会出现 静态代理对象量多、代码量大,从而导致代码复杂的问题,而动态代理只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码 更强的灵活性设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由JVM来实现,在使用时(调用目标对象方法时)才会动态创建动态代理类 & 实例,不需要事先实例化。

2.动态代理的缺点是什么?
答案:
效率低
相比静态代理中直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法。

应用场景局限
因为Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类 创建代理类即只能动态代理 实现了接口的类。

上一篇 下一篇

猜你喜欢

热点阅读