代理模式 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 中的动态代理主要用于实现服务接口的远程调用,它隐藏了底层的网络通信细节,使得客户端可以像调用本地服务一样调用远程服务。

总结

代理模式适用场景如下:

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

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

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

相关推荐
腥臭腐朽的日子熠熠生辉18 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian20 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之26 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen1 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端