代理模式使用场景:

  • 保护机制
  • 补充功能
  • 远程代理
  • 缓存代理
  • 数据库连接等频繁使用但操作少。

代理模式需要注意扩展,扩展成接口+实现类。同样的函数接口,不同的代理类有不同的实现方式。

1 解决的问题

  1. 无法直接访问对应对象的问题。如,科学上网。
  2. 客户端与数据库建立连接,但是仅仅只查询一个数据。高并发时建立与数据库连接会很卡。

2 代理模式

在不改变原始类 (被代理类) 的情况下,通过引入代理类为原始类附加功能,对过程进行访问控制

  • 服务接口类:声明公用方法,给后面两个角色实现,一般是接口形式;
  • 服务实现类:实现服务接口类的方法,最终的操作对象;
  • 代理类:实现服务实现类,包含对实现主题类对象的操作;

3 具体实现

3.1 静态代理

代理类的字节码在 运行前 就编译好。

// 抽象主题类
public interface IHttp {
    void request(String sendData);
    void onSuccess(String receivedData);
}
 
// 实现主题类
public class HttpUtil implements IHttp {
    @Override    public void request(String sendData) {        System.out.println("网络请求中...");    }
    @Override    public void onSuccess(String receivedData) {        System.out.println("网络请求完成。"); }
}
 
// 代理类
public class HttpProxy implements IHttp {
    private final HttpUtil httpUtil;
    public HttpProxy(HttpUtil httpUtil) {        this.httpUtil = httpUtil;    }
 
    @Override
    public void request(String sendData) {
        System.out.println("发送数据:" + sendData);
        httpUtil.request(sendData);
    }
    @Override
    public void onSuccess(String receivedData) {
        System.out.println("收到数据:" + receivedData);
        httpUtil.onSuccess(receivedData);
    }
}
 
// 测试
public class Client {
    @Test
    public void test() {
        HttpUtil httpUtil = new HttpUtil();
        HttpProxy proxy = new HttpProxy(httpUtil);
        proxy.request("request data");
        proxy.onSuccess("received result");
    }
}

3.2 动态代理

代理类的字节码在 运行时 由虚拟机中的程序自动创建。

Java 中,常见的动态代理实现方式有两种:

  • JDK动态代理 → 底层依赖反射机制,被代理类要实现接口方法,通过 invokeHandler 对所需方法进行增强;
  • CGLIB代理 → 利用 ASM 框架,修改字节码生成子类来处理;

3.2.1 JDK 动态代理示例

玩法如下:

  • ① 定义代理接口;
  • ② 真实对象实现这个代理接口;
  • ③ 定义动态代理类,实现 InvocationHandler 接口,重写 invoke()方法,做些小动作;
  • ④ 调用 Proxy 类创建的代理类;

代码示例如下

// 代理接口
public interface Shopping {
    void shopping(String thing);
    void pay();
}
 
// 实现代理接口的真实对象(这里定义了2个)
public class FirstShoppingImpl implements Shopping {
    @Override
    public void shopping(String thing) { System.out.println("在一号商家购买:" + thing); }
 
    @Override
    public void pay() { System.out.println("在一号商家付款"); }
}
public class SecondShoppingImpl implements Shopping {
    @Override
    public void shopping(String thing) { System.out.println("在二号商家购买:" + thing); }
 
    @Override
    public void pay() { System.out.println("在二号商家付款"); }
}
 
// 动态代理类
public class DynamicShoppingProxy implements InvocationHandler {
    private Shopping object;  // 委托类对象
 
    public DynamicShoppingProxy(Shopping object) {
        this.object = object;
    }
 
    /**
     * @param proxy 被代理的对象
     * @param method 被代理对象的方法
     * @param args 方法的参数
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("shopping")) {
            System.out.println("开始购物...");
            // 反射调用类里的实际方法,返回方法的返回值,没有返回值的话返回null
            method.invoke(object, args);
            System.out.println("结束购物...");
        } else if (method.getName().equals("pay")) {
            System.out.println("开始付款...");
            method.invoke(object, args);
            System.out.println("结束付款...");
        }
        return null;
    }
}
 
// 测试用例:调用Proxy类创建的代理类
public class DynamicProxyTest {
    public static void main(String[] args) {
        // 生成的动态代理文件保存到当前项目的com/sun/proxy目录下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
 
        // 创建动态代理实例
        DynamicShoppingProxy proxy1 = new DynamicShoppingProxy(new FirstShoppingImpl());
        DynamicShoppingProxy proxy2 = new DynamicShoppingProxy(new SecondShoppingImpl());
 
        // newProxyInstance()动态生成代理类,参数依次为:类加载器(要仅限代理的类)、被代理类实现的接口,动态代理实例
        Shopping s1 = (Shopping) (Proxy.newProxyInstance(Shopping.class.getClassLoader(),
                new Class[]{Shopping.class}, proxy1));
        System.out.println(s1.getClass().getName());
        s1.shopping("鞋子");
 
        Shopping s2 = (Shopping) (Proxy.newProxyInstance(Shopping.class.getClassLoader(),
                new Class[]{Shopping.class}, proxy2));
        System.out.println(s2.getClass().getName());
        s2.shopping("衣服");
        s2.pay();
    }
}

运行结果如下:

接着可以打开看看 $Proxy0 这个动态代理类:

static 代码块中获取代理方法,然后利用反射调用动态代理类的方法。看着比较简单,接着看下背后的原理。

3.2.2 JDK 动态代理的实现原理

跟下 Proxy 的 newProxyInstance() 方法:

先跟下 getProxyClass0() 查找或生成代理类的方法:

proxyClassCache.get()WeakCache.get()ProxyClassFactory.apply()

定位到了生成字节码的方法: ProxyGenerator.generateProxyClass()

定位到 generateClassFile (),分为三步:

  1. 为所有方法生成代理调度代码,将代理方法对象集合起来

2)为类中的方法生成字段信息和方法信息

3)生成最终类文件

以上就是 $Proxy0 类的大概生成流程,了解下即可,具体的细节真的是繁琐…

3.2.3 CGLIB 动态代理示例

4 应用场景

  • 数据库连接等频繁使用但操作较少的情况。
  • 保护代理 → 控制目标对象的访问,给不同用户提供不同的访问权限;
  • 智能引用代理 → 访问对象时附加额外操作,业务系统开发非功能性需求(监控、统计、日志等)
  • 虚拟代理 → 延迟初始化,创建小对象代理大对象(包含大量 IO 资源)的创建,大对象真正需要时才创建;
  • 远程代理 → 需要本地执行远程服务代码的场景,中间件领域,如远程代理框架 gRpc、Dubbo 等;
  • 缓存代理 → 缓存客户端请求结果并对缓存生命周期进行管理的场景;

5 经典应用例子

详解设计模式之结构型模式(下) - 知乎