当前位置: 首页 > article >正文

23种设计模式之代理模式

文章目录

    • 代理模式
    • Spring中那些地方使用了代理模式
    • 代理的分类
    • 模版方法模式也能添加额外的功能,与代理模式有什么区别
    • 静态代理-简单实现
    • jdk动态代理-简单实现
    • Spring是如何实现jdk动态代理的
    • cglib动态代理-简单实现
    • Spring是如何实现cglib动态代理的
    • jdk动态代理和cglib动态代理有什么区别
    • Spring是如何选择jdk动态代理或者cglib动态代理的
    • 总结-静态代理、动态代理

代理模式

在代理模式中,有一个代理对象和一个实际的目标对象。代理对象控制对目标对象的访问,并可以在访问目标对象之前或之后添加额外的逻辑。

在这里插入图片描述

Spring中那些地方使用了代理模式

在 Spring 框架中,有很多地方使用了代理模式,以下是一些主要的例子:
一、AOP(面向切面编程)
Spring AOP 是基于代理模式实现的。当为一个目标对象应用切面时,Spring 会创建一个代理对象,这个代理对象在目标方法执行前后插入额外的逻辑(如事务管理、日志记录、安全检查等)。
例如,当一个方法被标注为@Transactional时,Spring 会在运行时创建一个代理对象,在这个方法执行前开启事务,方法执行后提交或回滚事务。

二、远程服务调用
Spring 的RestTemplate在进行远程服务调用时,可能会使用代理模式来隐藏网络通信的复杂性。它可以让开发者像调用本地方法一样调用远程服务,而实际的网络请求是通过代理对象来处理的。

三、延迟加载
在处理一些大对象或者耗时的对象加载时,Spring 可以使用代理模式实现延迟加载。比如,当一个对象的属性是另一个复杂对象,并且这个复杂对象在真正需要的时候才进行加载,就可以使用代理对象来控制这个加载过程。
总的来说,代理模式在 Spring 中发挥了重要作用,使得框架更加灵活、可扩展,同时也提高了系统的性能和可维护性。

代理的分类

代理模式分为静态代理和动态代理。
一、静态代理
定义:
由程序员创建或特定工具生成代理类的源码,再对其编译。在程序运行前,代理类的.class 文件就已经存在了。
特点:
实现较为直接,代理类和目标对象的关系在编译期就确定了。
如果有多个不同的目标对象需要代理,需要为每个目标对象编写对应的代理类,代码冗余度较高。
一旦代理逻辑需要修改,就需要修改代理类的代码并重新编译。
示例:
假设存在一个接口Subject和其实现类RealSubject,以及一个代理类StaticProxy实现Subject接口。代理类中持有一个RealSubject对象的引用,在代理类的方法中调用目标对象的方法,并可以在前后添加额外的逻辑。

二、动态代理
定义:
在程序运行时,通过反射机制动态地创建代理类并实例化代理对象。
特点:
更加灵活,不需要为每个目标对象编写特定的代理类,可以根据不同的目标对象在运行时动态生成代理对象。
通常使用 Java 的反射 API 或者特定的代理框架(如 Java 的InvocationHandler接口结合Proxy类)来实现。
可以方便地在不修改目标对象代码的情况下,为目标对象添加额外的功能。

模版方法模式也能添加额外的功能,与代理模式有什么区别

一、实现方式不同

  • 模板方法模式:通过定义一个抽象类,其中包含一些抽象方法和一个模板方法。模板方法中定义了算法的框架,调用抽象方法来实现具体的步骤。子类通过重写抽象方法来实现特定的功能。在这个过程中,可以在模板方法中添加额外的功能,这些功能会在特定的步骤前后执行。
  • 代理模式:分为静态代理和动态代理。静态代理是由程序员创建代理类的源码,代理类持有目标对象的引用,并在代理方法中调用目标对象的方法,同时可以在前后添加额外的逻辑。动态代理是在程序运行时通过反射机制动态地创建代理类并实例化代理对象,通过实现InvocationHandler接口,在invoke方法中拦截目标对象的方法调用并添加额外功能。

二、适用场景不同

  • 模板方法模式:适用于算法框架相对固定,但某些步骤的具体实现可以变化的场景。例如,在一个数据处理流程中,数据读取、处理和存储的基本流程是固定的,但不同的数据类型可能需要不同的读取和处理方式。
  • 代理模式:适用于需要控制对目标对象的访问、增强目标对象功能或者进行远程调用等场景。比如,在网络访问中,使用代理模式可以隐藏网络通信的复杂性;在权限管理中,代理模式可以根据用户权限控制对目标对象的访问。

三、灵活性不同

  • 模板方法模式:一旦算法框架确定,修改起来相对较难,需要修改抽象类和子类的代码。但是对于特定步骤的实现,可以通过子类的重写来实现灵活的变化。
  • 代理模式:静态代理的灵活性相对较低,需要为每个目标对象编写特定的代理类。而动态代理则非常灵活,可以在运行时根据不同的目标对象动态地创建代理对象,并且可以方便地在不修改目标对象代码的情况下,为目标对象添加额外的功能。

静态代理-简单实现

以下是用阿里云对象存储服务(OSS)来举例说明静态代理的实现:

一、定义接口

interface OSSService {
    void uploadFile(String filePath);
}

二、真实实现类

public class RealOSSClient implements OSSService {
    @Override
    public void uploadFile(String filePath) {
        // 实际的 OSS 上传逻辑,这里只是模拟
        System.out.println("直接上传文件到OSS: " + filePath);
    }
}

三、静态代理类

public class OSSProxy implements OSSService {
    private final OSSService realOSSClient;

    public OSSProxy(OSSService realOSSClient) {
        this.realOSSClient = realOSSClient;
    }

    @Override
    public void uploadFile(String filePath) {
        System.out.println("上传到OSS之前,添加额外的功能。");
        realOSSClient.uploadFile(filePath);
        System.out.println("上传到OSS之后,添加额外的功能。");
    }
}

四、使用示例

public class Main {
    public static void main(String[] args) {
        OSSService realClient = new RealOSSClient();
        OSSService proxy = new OSSProxy(realClient);
        proxy.uploadFile("example.txt");
    }
}

执行结果

上传到OSS之前,添加额外的功能。
直接上传文件到OSS: example.txt
上传到OSS之后,添加额外的功能。

在这个例子中,静态代理类OSSProxy在上传文件前后添加了额外的输出信息,模拟了在实际使用 OSS 服务时可能需要添加的额外逻辑,比如记录日志、进行权限检查等操作,而不修改真实的 OSS 客户端实现类。

jdk动态代理-简单实现

这个例子使用了上面的接口和实现类。

创建动态代理处理器

public class DynamicProxyHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用之前,添加额外的功能。");
        Object result = method.invoke(target, args);
        System.out.println("调用之后,添加额外的功能。");
        return result;
    }
}

使用

    public static void main(String[] args) {
        OSSService ossService = new RealOSSClient();
        OSSService ossProxy = (OSSService) java.lang.reflect.Proxy.newProxyInstance(ossService.getClass().getClassLoader(),
                ossService.getClass().getInterfaces(),
                new DynamicProxyHandler(ossService));
        ossProxy.uploadFile("example.txt");
    }

执行结果

调用之前,添加额外的功能。
直接上传文件到OSS: example.txt
调用之后,添加额外的功能。

Spring是如何实现jdk动态代理的

在 Spring 中,JDK 动态代理主要是通过org.springframework.aop.framework.JdkDynamicAopProxy类实现的。Spring的动态代码实现方式和上面的是一样的都是实现InvocationHandler接口。

简化后的核心代码如下:

// 最终类,表示 JDK 动态代理的实现类,实现了 InvocationHandler 接口
final class JdkDynamicAopProxy implements InvocationHandler{
    // 存储配置信息
    private final AdvisedSupport advised;
    
    // 构造函数,接收配置信息并初始化
    public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
        this.advised = config;
    }
    
    @Nullable
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取目标对象源
        TargetSource targetSource = this.advised.targetSource;
        // 从目标对象源获取目标对象
        Object target = targetSource.getTarget();

        // 如果有拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, target.getClass());
        if (!chain.isEmpty()) {
            // 创建反射方法调用对象,传入代理对象、目标对象、方法、参数、目标对象类以及拦截器链
            MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, target.getClass(), chain);
            // 执行方法调用对象的 proceed 方法,即依次执行拦截器链中的拦截器和目标方法
            return invocation.proceed();
        } else {
            // 没有拦截器时直接调用目标对象的方法
            return method.invoke(target, args);
        }
    }
}

基于 JDK 的 AopProxy 实现 Spring AOP 框架说明
需注意,动态代理不能用于代理类中自行定义的方法,而仅适用于接口。
此类的对象应通过代理工厂获取,并由 AdvisedSupport 类进行配置。该类属于 Spring AOP 框架的内部实现,客户端代码无需直接使用。
若基础(目标)类是线程安全的,那么通过此类创建的代理也将是线程安全的。同时,只要所有顾问(包括建议和切入点)以及 TargetSource 都是可序列化的,代理也是可序列化的。

cglib动态代理-简单实现

这个例子使用了上面的接口和实现类。
1、创建方法拦截器

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用之前,添加额外的功能。");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("调用之后,添加额外的功能。");
        return result;
    }
}

2、使用 CGLIB 创建代理对象

    public static void main(String[] args) {
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        // 设置目标类
        enhancer.setSuperclass(RealOSSClient.class);
        // 设置方法拦截器
        Callback callback = new CglibInterceptor();
        enhancer.setCallback(callback);
        // 创建代理对象
        RealOSSClient proxyObject = (RealOSSClient) enhancer.create();
        // 调用代理对象的方法
        proxyObject.uploadFile("example.txt");
    }

3、执行结果

调用之前,添加额外的功能。
直接上传文件到OSS: example.txt
调用之后,添加额外的功能。

Spring是如何实现cglib动态代理的

cglib动态代理的实现类org.springframework.aop.framework.CglibAopProxy

下面是cglib创建代理的简化后的核心代码。

private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
    // 如果日志级别为跟踪级别,则输出正在创建 CGLIB 代理的信息以及目标源的信息
    if (logger.isTraceEnabled()) {
        logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
    }

    // 获取被代理的目标类
    Class<?> rootClass = this.advised.getTargetClass();
    // 断言目标类不为空,因为创建 CGLIB 代理需要目标类
    Assert.state(rootClass!= null, "Target class must be available for creating a CGLIB proxy");

    // 将初始代理超类设置为目标类
    Class<?> proxySuperClass = rootClass;
    // 如果目标类的名称包含 CGLIB 的类分隔符
    if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
        // 将代理超类设置为目标类的父类
        proxySuperClass = rootClass.getSuperclass();
        // 将目标类的所有接口添加到代理配置中
        for (Class<?> additionalInterface : rootClass.getInterfaces()) {
            this.advised.addInterface(additionalInterface);
        }
    }

    // 验证目标类,如果需要则进行相关处理
    validateClassIfNecessary(proxySuperClass, classLoader);

    // 创建 CGLIB 的 Enhancer 对象
    Enhancer enhancer = createEnhancer();
    // 如果类加载器不为空
    if (classLoader!= null) {
        // 设置 Enhancer 的类加载器
        enhancer.setClassLoader(classLoader);
        // 如果类加载器是可重新加载类的智能类加载器
        if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
            // 设置 Enhancer 不使用缓存
            enhancer.setUseCache(false);
        }
    }
    // 设置 Enhancer 的父类为目标类或目标类的父类
    enhancer.setSuperclass(proxySuperClass);
    // 设置 Enhancer 的接口为完整的被代理接口列表
    enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
    // 设置 Enhancer 的命名策略为 Spring 的命名策略
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    // 设置 Enhancer 尝试加载类
    enhancer.setAttemptLoad(true);
    // 设置 Enhancer 的生成策略为类加载器感知的生成策略
    enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

    // 获取回调对象数组
    Callback[] callbacks = getCallbacks(rootClass);
    // 创建回调对象类型数组
    Class<?>[] types = new Class<?>[callbacks.length];
    // 遍历回调对象数组,将每个回调对象的类型放入类型数组中
    for (int i = 0; i < types.length; i++) {
        types[i] = callbacks[i].getClass();
    }

    // 创建代理回调过滤器
    ProxyCallbackFilter filter = new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset);
    // 设置 Enhancer 的回调过滤器
    enhancer.setCallbackFilter(filter);
    // 设置 Enhancer 的回调类型数组
    enhancer.setCallbackTypes(types);

    // 如果只需要代理类,则创建代理类;否则创建代理类并实例化
    return classOnly? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks);
}

jdk动态代理和cglib动态代理有什么区别

JDK 动态代理和 CGLIB 动态代理主要有以下区别:

一、实现原理

  • JDK 动态代理:利用 Java 的反射机制,在运行时创建一个实现了目标对象所实现的所有接口的代理类。代理对象通过调用InvocationHandlerinvoke方法来实现对目标对象方法的调用,并可以在这个方法中添加额外的逻辑。
  • CGLIB 动态代理:通过字节码生成技术,在运行时动态生成目标对象的子类来实现代理。它会重写目标对象的方法,在方法中插入额外的逻辑。

二、目标对象要求

  • JDK 动态代理:要求目标对象必须实现至少一个接口。如果目标对象没有实现任何接口,就无法使用 JDK 动态代理。
  • CGLIB 动态代理:不要求目标对象实现任何接口,可以对任何类进行代理。

三、性能

  • JDK 动态代理:相对来说性能稍低一些,因为它是通过反射机制来调用目标对象的方法。但是在 JDK 1.8 及以后的版本中,对反射机制进行了优化,性能有了一定的提升。
  • CGLIB 动态代理:性能通常比 JDK 动态代理高一些,因为它是通过生成子类的方式来实现代理,直接调用方法,没有反射的开销。但是 CGLIB 在生成代理类和加载类的时候会有一定的性能开销。

四、代码复杂性

  • JDK 动态代理:代码相对简单,只需要实现InvocationHandler接口,并在invoke方法中编写代理逻辑。
  • CGLIB 动态代理:代码相对复杂一些,需要处理字节码生成和类加载等问题。

五、应用场景

  • JDK 动态代理:适用于目标对象实现了接口的情况,尤其是在需要对多个不同的接口进行代理时,使用 JDK 动态代理比较方便。
  • CGLIB 动态代理:适用于目标对象没有实现接口,或者需要对具体类进行代理的情况。例如,在对第三方库中的类进行代理时,如果这些类没有实现接口,就可以使用 CGLIB 动态代理。

Spring是如何选择jdk动态代理或者cglib动态代理的

用哪个动态代理是由DefaultAopProxyFactory决定的。下面是这个工厂的代码

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 如果配置满足优化条件、指定了代理目标类或者没有用户提供的代理接口
    // isOptimize-是否开启优化策略 , isProxyTargetClass-目标类是接口则为true,hasNoUserSuppliedProxyInterfaces-有实现类为true
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        // 获取配置中的目标类
        Class<?> targetClass = config.getTargetClass();
        // 如果目标类为空,抛出异常
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        // 如果目标类是接口、是代理类或者是 Lambda 类
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
            // 返回 JDK 动态代理对象
            return new JdkDynamicAopProxy(config);
        }
        // 返回 CGLIB 代理对象
        return new ObjenesisCglibAopProxy(config);
    } else {
        // 否则返回 JDK 动态代理对象
        return new JdkDynamicAopProxy(config);
    }
}

总结-静态代理、动态代理

静态代理需为每个目标对象编写特定代理类,灵活性低。JDK 动态代理利用反射,适用于有接口的目标对象。CGLIB 动态代理通过字节码生成,不要求目标对象有接口。动态代理比静态代理更灵活,适应不同场景。


http://www.kler.cn/a/287935.html

相关文章:

  • linux企业中常用NFS、ftp服务
  • Nginx: 实现Websocket代理
  • outline 分析
  • 【Framework系列】UnityEditor调用外部程序详解
  • AdaBoost 二分类问题
  • 跟我学C++中级篇——Design Patterns的通俗说法
  • 如何写接口自动化测试断言?
  • SpringBoot 数据访问-jpa
  • 【CSS】如何写渐变色文字并且有打光效果
  • 嵌入式系统基础知识介绍
  • DAY65
  • 基于STM32和OpenCV的车载智能导航系统:实现实时交通标志与信号识别与预警(代码示例)
  • 将string类中能够实现的操作都封装在MyString类中
  • 如何保证Redis与Mysql双写一致性?
  • 【话题讨论】VS Code:倍增编程动力,实现效率飞跃
  • TCP 和 UDP 区别
  • c++ 定义宏常量
  • 有什么简单方便的cad编辑器?2024快速进行cad编辑的软件合集
  • 神经网络训练不起来怎么办(五)| Batch Normalization
  • 【无标题】html前段小知识点
  • Django Admin对自定义的计算字段进行排序
  • hugging face 利用现有模型进行预测
  • C语言 strlen求字符串长度
  • Linux驱动(三):字符设备驱动之杂项
  • Go wv(WebView2) GUI框架介绍和使用
  • 【Python报错已解决】“NameError: name ‘re‘ is not defined”