Java中的代理

251 阅读4分钟

概述


代理是☝️种设计模式,为某个对象提供☝️个代理,以控制对该对象的访问。代理模式在 Spring、Dubbo 这些框架、组件中被广泛地使用来实现自己的特性,例如 Spring AOP。代理可以分为两种类型:

  1. 静态代理:在编译阶段已经确定代理关系,简而言之委托类、代理类是直接硬编码,代理类直接存在编译生成的二进制文件中。
  2. 动态代理:代理类的字节码文件是在运行过程中动态生成。

静态代理

下面是静态代理的一个例子。类 Entruster 作为委托者,我们需要将它的方法进行代理。我们可以定义一个代理类 Proxy ,并继承委托类(如2所示),重写委托类中的方法(如5所示)。为了能够将方法代理到委托对象上,代理对象需要持有一个委托类的实例(如3中所示),我们可以通过代理类的构造方法引入委托对象(如4所示)。当然,取而代之的,也可以通过提供 Setter 来设置。这样之后,代理对象只要在重写的方法中通过调用对应的委托对象的方法来实现代理(如5、6所示)。

我们可以在调用代理对象方法之前、之后加上额外的代码。例如可以在调用前后记录时间来监测接口所花费的时间、在调用前后加入日志处理来跟踪接口调用情况、在调用之前加入权限控制的代码、调用之后对返回结果信息进行脱敏处理等等。

// 1.
// * 委托类
// 是方法的实际执行者
public class Entruster {
    @Override
    public void method() {
        // do something...
    }
}
// 2.
// * 代理类
// 将方法的调用代理到委托类上
public class Proxy extends Entruster{
    // 3.
    // * 委托对象
    // 真正做事的对象,可以称之为 realSubject。
    private Entruster entruster;
    // 4.
    public Proxy(Entruster entruster) {
        this.entruster = entruster;
    }
    // 5.
    // 代理类实现委托类的方法,这样代理类就拥有了和委托类相同的方法
    @Override
    public void method() {
        // do something before calling real-subject's method
        // ...
        // 6.
        // 执行委托对象的方法,完成代理
        entruster.method();
        // do something after calling real-subject's method
        // ...
    }
}
// main方法中可以这样调用
// 7.
public static void main(Strings[] args) {
    (Entruster)entruster = new Proxy(new Entruster());
    // 调用代理对象的 method 方法
    // 代理对象中的 method 方法会去调用委托对象的 method 方法,完成代理工作
    entruster.method();
}

动态代理

静态代理存在弊端。只有当代理类能够被继承并重写,或者代理类存在接口能够被委托类实现的情况下才能够实现;同时,如果需要实现很多代理类,意味着需要很多额外的代码,维护起来比较麻烦。而动态代理可以动态生成代理类,解决了需要✍️大量静态代码的问题。

动态代理主要有:JDK Proxy 、CgLib。

JDK Proxy

public class ProxyFactory implements InvocationHandler {
    // 代理对象
    // called real-subject
    private T realSubject;
    private ProxyFactory(T realSubject) {
        this.realSubject = realSubject;
    }
    /**
     * produce a proxy bound with an InvocationHandler instance which was bound with the real-subject
     * @param realSubject 代理对象
     * @param  代理类
     */
    public static  T bind(T realSubject) {
        // create the proxy
        return (T) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                new ProxyFactory<>(realSubject));
    }
    /**
     * 官方对 invoke(...) 方法解释如下:
     * Processes a method invocation on a proxy instance and returns the result. 
     * This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // here can do something before calling real-subject's method
        Object result = method.invoke(realSubject, args);
        // here can do something after calling real-subject's method
        return result;
    }
}
// 委托类接口
interface Entrust {
    void method();
}
// 委托类实现
class EntrustImpl implements Entrust {
    @Override
    public void method() {
        System.out.println("Hello ?");
    }
}
// main 方法中可以这样获取、使用委托对象
public static void main(String[] args) {
    // 调用 bind(...) 方法让 ProxyFactory 生成一个代理对象。
    // 可以直接使用这个代理对象,就像使用委托对象一样。
    ((Entrust)(ProxyFactory.bind(new EntrustImpl()))).method();
}

CgLib

CgLib是一个高性能的代码生成工具,底层基于小而快的字节码处理框架ASM来生产新的类。CgLib是一个被广泛使用的代理框架,例如 Spring AOP 中也可以使用CgLib生成代理类,对切点进行增强操作。 juejin.cn/post/684490…