代理模式 Proxy

代理模式 Proxy

代理模式(Proxy):为其他对象提供一种代理,以控制对这个对象的访问。

The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it.

啥意思呢?代理的本意是指一个人或组织代表另一个人或组织执行某些任务、权利或职责。比如销售代理可以帮着销售供应商的产品。

先看一下 UML 类图:

代理模式角色如下:

Subject(抽象主题):定义了 RealSubjectProxy 的共同接口,真实主题和代理都实现了这个接口。

Real Subject(真实主题):定义了真正的对象,Proxy 需要代理的类。

Proxy(代理):代理对象,维护一个指向 RealSubject 的引用,控制对 RealSubject 的访问,并在必要时执行一些额外的操作。

类比销售代理(Proxy)和供应商(Real Subject):销售代理和供应商的目的都是为了销售产品(有相同的行为),而且本地客户一般不和供应商打交道,有啥事都是找本地销售代理,本地销售代理被授权处理相关事宜(控制对这个对象的访问)。

总而言之,代理类隔离了客户端与真实主题。

为啥要隔离呢?这样做有什么好处呢?下面看一下代理模式基本实现。

基本实现

抽象主题 Subject

Subject 类,定义了 RealSubjectProxy 的共用接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy

java 复制代码
public interface Subject {
    void request();
}

真实主题 RealSubject

RealSubject 类,定义 Proxy 所代表的真实实体。

java 复制代码
public class RealSubject implements Subject{
    
    @Override
    public void request() {
        System.out.println("真实的请求");
    }
}

代理 Proxy

Proxy 类,保存一个引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代真实主题。

java 复制代码
public class Proxy implements Subject{

    private Subject realSubject;

    @Override
    public void request() {
        if (realSubject==null)
            realSubject=new RealSubject();

        preRequest();
        realSubject.request();
        postRequest();
    }
    
    private void preRequest(){
        System.out.println("访问真实主题之前的预处理");
    }
    private void postRequest(){
        System.out.println("访问真实主题之后的后续处理");
    }

}

客户端 Client

客户端可以像使用真实主题一样使用代理。

java 复制代码
Subject subject = new Proxy();
subject.request();

输出结果如下:

java 复制代码
访问真实主题之前的预处理
真实的请求
访问真实主题之后的后续处理

上述代码,演示了代理者可以为目标主体的行为添加附加方法,进行行为增强

更重要的是,如果附加方法的逻辑一样,多个真实主题都想要这种附加,那同一个代理类就可以代理这些主题了,不需要引入其他代理类:

更进一步,如果不同主题的增强逻辑都一样,那应该怎么做呢?

代理者只有继承或实现目标主题 ,才能和目标主题 保持一样的行为,才能让客户端使用代理类 像使用目标主题一样,那现在不同主题的增强逻辑都一样,那有几个主题就搞几个代理类吗?哪怕它们的增强逻辑都一样。

这就需要动态代理技术了。

动态代理 Dynamic Proxy

动态代理(Dynamic Proxy)是指在程序运行时动态地创建代理对象的一种机制,而不需要在编译时就确定代理类的类型。JDK 原生有一种动态代理,Cglib 也有一种动态代理,下面分别看一下:

JDK 动态代理

JDK 动态代理是基于接口的动态代理机制,要求被代理的类必须实现一个或多个接口,代理类是通过java.lang.reflect.Proxy 类的 newProxyInstance 方法动态生成的。

java 复制代码
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h){...}

该方法中有三个参数:

ClassLoader loader :被代理的对象所属类的类加载器;

Class[] interfaces :被代理的对象所属类实现的接口;

InvocationHandler h :代表增强的逻辑,当代理对象的方法被调用时,其invoke 方法就会被调用。

JDK 动态代理 将增强的逻辑封装到 InvocationHandler 中:

java 复制代码
public class MyHandler implements InvocationHandler {

    // 目标主题
    private Subject subject;

    public ProxyHandler(Subject subject){
        this.subject = subject;
    }

    /**
     *
     * @param proxy 代理对象的引用
     * @param method 代理对象执行的方法
     * @param args 代理对象执行方法的参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("访问真实主题之前的预处理");
        // 由于代理对象与目标主题实现了相同的接口,所以也可以反射调用目标主题的 method
        Object ret = method.invoke(subject, args);
        System.out.println("访问真实主题之后的后续处理");
        return ret;    
    }
}

有了这个增强的地方,就可以创建任意目标主题的代理对象了:

java 复制代码
// 目标主题
Subject subject = new RealSubject();
Class<? extends Subject> clazz = subject.getClass();

// 使用 JDK 动态代理,生成代理对象
Subject proxy = (Subject) java.lang.reflect.Proxy.newProxyInstance(
        clazz.getClassLoader(), // 被代理的对象所属类的类加载器
        clazz.getInterfaces(),  // 被代理的对象所属类实现的接口
        new MyHandler(subject)); // 增强的逻辑

// 客户端使用代理类像使用目标主题一样
proxy.request();

Cglib 动态代理

Cglib(Code Generation Library)是一个强大的字节码生成库,CGLIB动态代理不要求被代理的类实现接口,它通过继承的方式创建代理类。

和 JDK 动态代理一样,Cglib 也有一个封装增强逻辑的地方:MethodInterceptor。当代理对象的方法被调用时,会被转发到MethodInterceptorintercept方法中。

java 复制代码
public class MyInterceptor implements MethodInterceptor {

    // 目标主题
    private Object subject;

    public MyInterceptor(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("访问真实主题之前的预处理");
        // 由于代理对象与目标主题是继承关系,所以也可以反射调用目标主题的 method
        Object ret = method.invoke(subject, objects);
        System.out.println("访问真实主题之后的后续处理");
        return ret;    
    }
}

CGLIB动态代理的核心类是net.sf.cglib.proxy.Enhancer,其 create 方法可以创建代理类:

java 复制代码
public static Object create(Class type, Callback callback){//...}

同 JDK 动态代理相似,Cglib 动态代理的内容相对较少,它只需要传入两个东西:

Class type :被代理的对象所属类的类型

Callback callback :增强的代码实现,由于一般情况下我们都是对类中的方法增强,所以在传入 Callback 时通常选择这个接口的子接口 MethodInterceptor

java 复制代码
// 目标主题
Subject subject = new RealSubject();

// 使用 Cglib 动态代理,生成代理对象
Subject proxy = (Subject) net.sf.cglib.proxy.Enhancer.create(
        subject.getClass(), // 被代理的对象所属类的类型
        new MyInterceptor(subject) // 增强的代码实现
);

// 客户端使用代理类像使用目标主题一样
proxy.request();

使用 Cglib 注意点如下:

⨳ 被代理的类不能是 final 的:Cglib 动态代理会修改字节码创建子类,final 类型的 Class 无法继承;

⨳ 被代理的类必须有默认无参构造方法:Cglib 底层反射创建对象时拿不到构造方法参数;

源码鉴赏

Spring 之 AOP

Spring AOP(面向切面编程)是 Spring 框架的一个关键模块,它通过动态代理技术为应用程序提供了横切关注点的分离能力,允许在方法执行前、后或异常抛出时,执行特定的代码。

Spring AOP 将增强逻辑写到切面(Aspect)中,AOP代理工厂(简单工厂模式)会根据情况选择JDK 还是 Cglib 动态代理。

默认使用Cglib,如果要代理的本身就是接口,或者已经是被 JDK 动态代理了的代理对象,则使用 JDK动态代理。

Mybatis 之 Mapper

MyBatis 使用了JDK动态代理技术来创建 Mapper 接口的代理对象,从而实现了对 SQL 语句的自动映射和执行。

MapperProxy 代码节选如下:

java 复制代码
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } // catch ......
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

就是目标主题没有实现类,也可以使用 JDK动态代理技术生成接口的代理类!!!

Dubbo 之 ProxyFactory

Dubbo 是一个分布式服务框架,它支持使用动态代理来实现远程服务调用。在 Dubbo 中,动态代理主要用于代理服务接口,以便客户端可以透明地调用远程服务,而无需关心底层的网络通信细节。

ProxyFactory 是Dubbo 中的代理工厂接口,用于创建服务接口的代理对象。

当客户端调用服务接口的方法时,Dubbo 会动态生成代理对象,并将调用请求转发给代理对象。代理对象负责将调用请求封装成远程调用请求,并通过底层的网络通信框架(如 Netty)发送到远程服务端,然后接收服务端的响应,并将响应返回给客户端。

总的来说,Dubbo 中的动态代理主要用于实现服务接口的远程调用,它隐藏了底层的网络通信细节,使得客户端可以像调用本地服务一样调用远程服务。

总结

代理模式适用场景如下:

保护目标对象:代理可以控制对对象的访问权限,限制对对象的直接访问,增强安全性。

增强目标对象:代理可以通过在访问对象前后进行一些操作,如缓存、延迟加载等,从而提升系统的性能。

代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

相关推荐
大圣数据星球2 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink
suweijie7682 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿3 小时前
List深拷贝后,数据还是被串改
java
思忖小下3 小时前
梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
设计模式·模板方法模式·eit
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹7 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫8 小时前
泛型(2)
java
超爱吃士力架8 小时前
邀请逻辑
java·linux·后端
南宫生8 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论