代理模式的实现

1. 引言

1.1 背景

代理模式(Proxy Pattern)是一种常用的设计模式,它允许通过一个代理对象来控制对另一个对象的访问。在面向对象编程的框架中,代理模式被广泛应用,尤其在Spring框架的AOP(面向切面编程)功能中,用于实现横切关注点的模块化,如事务管理、日志记录、安全检查等。

通过代理模式,我们能够将复杂的功能从主要业务逻辑中剥离出来,使代码更简洁易读。同时,它还能够在不修改原有代码的基础上,动态地增加新功能或优化现有功能,使代码更灵活可扩展。

1.2 目的

本文将详细介绍代理模式的基本概念、实现步骤。通过本篇文章,你将能够理解代理模式的工作原理,并学会如何在实际项目中有效地利用它。

2. 何为代理模式?

代理模式就像是生活中的中介或代理人,他们代表我们处理一些事务,让我们能够更方便、更高效地完成任务。想象一下,你想要买一套房子,但你没有时间或者不熟悉购房流程,这时候你可能会找一个房产中介来帮你处理这些事情。房产中介就是这个场景中的“代理”,而你则是“客户端”,房子和卖家则是“真实对象”。

在这个例子中,房产中介(代理)会:

- 代表你与卖家沟通:中介会代替你与卖家协商价格、查看房子状况等,这样你就不需要亲自去做这些事情。
- 提供额外的服务:中介可能会提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程。
- 控制访问:中介可能会筛选掉一些不符合你要求的房子,只向你推荐合适的房源,这样你就不需要自己去处理大量的信息。
- 延迟处理:如果你暂时没有时间去看房,中介可以先帮你预约,等到你有时间再去,这样就避免了资源的浪费。


通过房产中介这个代理,你可以在不直接与卖家接触的情况下,完成购房的任务,同时还能享受到额外的服务和便利。

在软件开发中,代理模式也是类似的道理。比如,一个处理敏感数据的应用程序,可以通过代理来控制对数据的访问,确保只有授权的用户才能查看或修改数据。或者,一个需要处理大量计算的应用,可以通过代理来实现计算的延迟加载,只在真正需要时才进行计算,从而提高系统的效率。

2.1 代理模式的主要角色

  1. Subject(主体):定义了RealSubjectProxy的共同接口,这样在任何使用RealSubject的地方都可以使用Proxy
  2. RealSubject(真实主体):定义了Proxy所代表的真实对象。
  3. Proxy(代理):持有一个RealSubject的引用,并提供与RealSubject相同的接口,这样代理就可以代替RealSubject。代理对象可以在调用RealSubject的方法前后执行额外的操作。
// 1. 主接口
public interface Subject {
    void house();
}


// 2. 真实对象类
public class RealSubject implements Subject {

    @Override
    public void house() {
        System.out.println("买家:筛选出想要的房子");
    }
}

// 3. 代理类
public class Proxy implements Subject {

    private RealSubject realSubject;


    @Override
    public void house() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.house();
        postRequest();
    }

    private void preRequest() {
        System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");
    }

    private void postRequest() {
        System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");
    }
}

// 4. 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.house();
    }
}

2.2 代理模式的应用场景

  • 远程代理:代表一个位于不同地址空间的对象。
  • 虚拟代理:根据需要创建开销很大的对象。
  • 保护代理:控制对原始对象的访问,用于对象应该有不同的访问权限的情况。
  • 智能引用:在访问对象时执行额外的操作,例如计算引用次数。

2.3 代理模式的主要类型

代理模式通常可以分为两种主要类型:静态代理和动态代理。

静态代理(Static Proxy)

静态代理是指在编译时就已经确定的代理类。静态代理类需要手动编写,它实现了与目标对象相同的接口,并在代理类中持有一个目标对象的引用。

静态代理的优点是实现简单,容易理解,但缺点是每当需要代理一个新的接口或类时,都需要手动创建一个新的代理类,这会导致代码冗余和维护成本增加。

代理模式的默认实现通常是静态代理,因为静态代理是最直观和最容易理解的方式:

// 1. 主接口
public interface Subject {
    void hourse();
}


// 2. 真实对象类
public class RealSubject implements Subject {
    @Override
    public void hourse() {
        System.out.println("买家:筛选出想要的房子");
    }
}

// 3. 代理类
public class Proxy implements Subject {
    private RealSubject realSubject;


    @Override
    public void hourse() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.hourse();
        postRequest();
    }

    private void preRequest() {
        System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");
    }

    private void postRequest() {
        System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");
    }
}

// 4. 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.hourse();
    }
}

动态代理(Dynamic Proxy)

动态代理是指在运行时动态生成的代理类。动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时创建代理对象。

动态代理的优点是灵活性高,可以为任意接口或类创建代理对象,而无需手动编写代理类。

Java提供了两种动态代理的实现方式:基于接口的动态代理(使用java.lang.reflect.Proxy类)和基于子类的动态代理(使用CGLIB库)。

我们还是基于买房卖房中介的场景,假设我们有一个房地产中介系统,其中有两个角色:买家和卖家。买家和卖家都可以通过中介进行交易。我们希望在交易过程中添加一些额外的功能,比如记录交易日志、检查交易资格等。

1. 基于接口的动态代理(买家)

基于接口的动态代理是Java标准库中提供的实现方式,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public interface Buyer {
    void buyHouse(String houseId);
}


public class RealBuyer implements Buyer {
    @Override
    public void buyHouse(String houseId) {
        System.out.println("买方:需要买的房子的ID是: " + houseId);
    }
}


public class BuyerInvocationHandler implements InvocationHandler {
    private Object realBuyer;

    public BuyerInvocationHandler(Object realBuyer) {
        this.realBuyer = realBuyer;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("买方中介BuyerInvocationHandler:检查买方资格");
        Object result = method.invoke(realBuyer, args);
        System.out.println("买方中介BuyerInvocationHandler:日志记录买方的需求");
        return result;
    }
}

 2. 基于子类的动态代理(卖家)

基于子类的动态代理通常使用第三方库,如CGLIB(Code Generation Library)。CGLIB通过生成目标类的子类来实现代理。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Seller {
    public void sellHouse(String houseId) {
        System.out.println("卖家: 需要售卖的房子ID: " + houseId);
    }
}


public class SellerMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("卖方中介SellerMethodInterceptor:检查卖方资格");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("卖方中介SellerMethodInterceptor:日志记录卖方的需求");
        return result;
    }
}

3. 这样,买方和卖方和中介(代理类)的关系就串起来了,形成一个完整的房地产中介系统

public class Client {
    public static void main(String[] args) {
        // 买家代理
        RealBuyer realBuyer = new RealBuyer();
        InvocationHandler buyerHandler = new BuyerInvocationHandler(realBuyer);
        Buyer buyerProxy = (Buyer) Proxy.newProxyInstance(
                realBuyer.getClass().getClassLoader(),
                realBuyer.getClass().getInterfaces(),
                buyerHandler);
        buyerProxy.buyHouse("123");

        // 卖家代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Seller.class);
        enhancer.setCallback(new SellerMethodInterceptor());
        Seller sellerProxy = (Seller) enhancer.create();
        sellerProxy.sellHouse("456");
    }
}

3. Spring框架中代理模式实现AOP

在Spring框架中,代理模式被广泛应用于AOP(面向切面编程)。在此之前,先简单的过一下什么是AOP编程吧,好有个清晰的认知。

3.1 何为面向切面编程?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过允许程序员模块化横切关注点(cross-cutting concerns)来提高代码的模块性。横切关注点是指那些影响多个模块的功能,如日志记录、事务管理、安全性检查等,这些功能通常会跨越多个模块,导致代码重复和耦合度增加。

3.2 AOP的核心概念

AOP通过以下几个核心概念来实现横切关注点的模块化:

  1. 切面(Aspect):一个模块化的横切关注点。切面可以包含多个通知(advice)和切入点(pointcut)。

  2. 通知(Advice):定义了切面在特定连接点(join point)上执行的动作。通知有多种类型,如前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)等。

  3. 切入点(Pointcut):定义了通知应该应用的连接点的集合。切入点通过匹配特定的方法或代码位置来确定通知的执行时机。

  4. 连接点(Join Point):程序执行过程中的一个特定点,如方法调用、异常抛出等。在AOP中,连接点是通知可以插入的地方。

  5. 引入(Introduction):允许向现有类添加新的方法或字段,从而在不修改现有代码的情况下扩展类的功能。

  6. 目标对象(Target Object):包含连接点的对象,也就是被代理的对象。

  7. 代理(Proxy):在目标对象上应用切面后创建的对象。代理对象负责在调用目标对象的方法时插入通知。

3.3 AOP的主要优点包括

代码重用:通过将横切关注点模块化为切面,可以在多个模块中重用这些功能。
降低耦合度:将横切关注点从业务逻辑中分离出来,降低了代码的耦合度。
提高可维护性:模块化的横切关注点使得代码更易于理解和维护。

3.4 AOP的实现流程

Spring AOP支持两种类型的代理:基于接口的动态代理和基于子类的动态代理(CGLIB)。

我们依旧以买家卖家和中介为例,分为四步实现:

1. 定义买卖双方的接口和各自的实现类

2.  创建一个切面类

3. 配置Spring上下文

4. 在客户端测试代理模式是否生效

首先,我们定义买家和卖家接口及其实现类:

public interface Buyer {
    void buyHouse(String houseId);
}

public class RealBuyer implements Buyer {
    @Override
    public void buyHouse(String houseId) {
        System.out.println("买方:需要买的房子的ID是: " + houseId);
    }
}

public interface Seller {
    void sellHouse(String houseId);
}

public class RealSeller implements Seller {
    @Override
    public void sellHouse(String houseId) {
        System.out.println("卖家: 需要售卖的房子ID: " + houseId);
    }
}

接下来,我们创建切面类,用于在买家和卖家操作前后添加额外的逻辑:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAspect {

    @Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void beforeTransaction() {
        System.out.println("TransactionAspect: 检查是否有交易资格");
    }

    @After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void afterTransaction() {
        System.out.println("TransactionAspect: 日志记录交易双方的需求");
    }
}

我们需要配置Spring上下文,启用AOP和组件扫描:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    @Bean
    public Buyer buyer() {
        return new RealBuyer();
    }

    @Bean
    public Seller seller() {
        return new RealSeller();
    }

    @Bean
    public TransactionAspect transactionAspect() {
        return new TransactionAspect();
    }
}

最后,我们编写一个测试类来验证代理模式是否生效:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Client {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Buyer buyer = context.getBean(Buyer.class);
        Seller seller = context.getBean(Seller.class);

        buyer.buyHouse("123");
        seller.sellHouse("456");
    }
}

虽然Spring AOP默认使用JDK动态代理来实现AOP,但也可以通过配置强制使用CGLIB来实现。

依旧以买家卖家和中介为例,分为四步实现:

1. 配置Spring上下文

2.  定义切面类

3. 定义目标类

4. 在客户端测试代理模式是否生效

首先,我们需要在Spring配置类中启用AOP并设置proxyTargetClass属性为true,以强制使用CGLIB代理。

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // 其他配置
}

接下来,我们创建一个切面类,并使用@Aspect注解标记。这个切面类将包含在买卖双方操作前后执行的通知。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAspect {

    @Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void beforeTransaction() {
        System.out.println("TransactionAspect: 检查是否有交易资格");
    }

    @After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void afterTransaction() {
        System.out.println("TransactionAspect: 日志记录交易双方的需求");
    }
}

定义买家和卖家类,这些类不需要实现任何接口。

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class Buyer {

    public void buyHouse(String houseId) {
        System.out.println("买方:需要买的房子的ID是: " + houseId);
    }
}

@Service
public class Seller {

    public void sellHouse(String houseId) {
        System.out.println("卖家: 需要售卖的房子ID: " + houseId);
    }
}

最后,我们编写一个测试类来验证CGLIB代理是否生效。

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.Buyer;
import com.example.service.Seller;

public class Client {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Buyer buyer = context.getBean(Buyer.class);
        Seller seller = context.getBean(Seller.class);

        buyer.buyHouse("123");
        seller.sellHouse("456");
    }
}

4. 总结

讲了这么多,我们也可以看出来代理模式是一种强大的设计模式,它通过引入代理对象来控制对目标对象的访问,并可以在不改变目标对象的情况下添加额外的功能。代理模式在许多框架和库中得到了广泛应用,如Spring AOP、Hibernate等。通过合理使用代理模式,可以提高代码的模块化、可维护性和灵活性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777520.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python的招聘数据分析与可视化管理系统-计算机毕业设计源码55218

摘要 随着互联网的迅速发展,招聘数据在规模和复杂性上呈现爆炸式增长,对数据的深入分析和有效可视化成为招聘决策和招聘管理的重要手段。本论文旨在构建一个基于Python的招聘数据分析与可视化管理系统。 该平台以主流招聘平台为数据源,利用Py…

arm架构安装chrome

在ARM架构设备上安装谷歌软件或应用通常涉及到几个步骤,这取决于你要安装的具体谷歌产品,比如谷歌浏览器、Google Play服务或者是其他谷歌开发的软件。下面我会给出一些常见的指导步骤,以安装谷歌浏览器为例: 在Linux ARM64上安装…

平价蓝牙耳机推荐有哪些?四大超值平价蓝牙耳机品牌盘点

市面上的蓝牙耳机品牌繁多,价格差异巨大,对于预算有限但又不想牺牲音质和使用体验的消费者来说,寻找到既平价又性能出色的蓝牙耳机无疑是一项挑战,那么在平价蓝牙耳机推荐有哪些?面对这个疑问,作为真无线蓝…

【图解大数据技术】Hive、HBase

【图解大数据技术】Hive、HBase Hive数据仓库Hive的执行流程Hive架构数据导入Hive HBaseHBase简介HBase架构HBase的列式存储HBase建表流程HBase数据写入流程HBase数据读取流程 Hive Hive是基于Hadoop的一个数据仓库工具,Hive的数据存储在HDFS上,底层基于…

CSS - 深入理解选择器的使用方式

CSS基本选择器 通配选择器元素选择器类选择器id 选择器 通配选择器 作用:可以选中所有HTML元素。语法: * {属性名;属性值; }举例: /* 选中所有元素 */ * {color: orange;font-size: 40px; }在清除样式方面有很大作用 元素选择器…

实现桌面动态壁纸(二)

目录 前言 一、关于 WorkerW 工作区窗口 二、关于窗口关系 2.1 窗口以及窗口隶属关系 2.2 桌面管理层窗口组分简析 2.3 厘清两个概念的区别 2.4 关于设置父窗口 三、编写代码以供在 Vista 上实现 3.1 方法二:子类化并自绘窗口背景 四、初步分析桌面管理层…

【音视频 | RTSP】RTSP协议详解 及 抓包例子解析(详细而不赘述)

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…

免密ssh和自定义服务器名字【远程连接服务器】

免密ssh和自定义服务器名字【远程连接服务器】 免密ssh和自定义服务器名字【远程连接服务器】服务器添加本地公钥ssh-copy-id使用别名登录config 免密ssh和自定义服务器名字【远程连接服务器】 原理 实现免密登录需要 本地的公钥id_rsa.pub放在服务器上的 authorized_keys 文件…

NTP协议格式解析

1. NTP时间戳格式 SNTP使用在RFC 1305 及其以前的版本所描述标准NTP时间戳的格式。与因特网标准标准一致, NTP 数据被指定为整数或定点小数,位以big-endian风格从左边0位或者高位计数。除非不这样指定,全部数量都将设成unsigned的类型&#…

边缘概率密度、条件概率密度、边缘分布函数、联合分布函数关系

目录 二维随机变量及其分布离散型随机变量连续型随机变量边缘分布边缘概率密度举例边缘概率密度 条件概率密度边缘概率密度与条件概率密度的区别边缘概率密度条件概率密度举个具体例子 参考资料 二维随机变量及其分布 离散型随机变量 把所有的概率,都理解成不同质量…

【Rust入门】生成随机数

文章目录 前言随机数库rand添加rand库到我们的工程生成一个随机数示例代码 总结 前言 在编程中,生成随机数是一种常见的需求,无论是用于数据分析、游戏开发还是模拟实验。Rust提供了强大的库来帮助我们生成随机数。在这篇文章中,我们将通过一…

huggingface笔记:gpt2

0 使用的tips GPT-2是一个具有绝对位置嵌入的模型,因此通常建议在输入的右侧而不是左侧填充GPT-2是通过因果语言建模(CLM)目标进行训练的,因此在预测序列中的下一个标记方面非常强大 利用这一特性,GPT-2可以生成语法连…

并发编程中常见的锁

一、锁的分类 1.1 悲观锁和乐观锁 乐观锁: 定义: 假设在绝大多数情况下,对共享资源的访问是不会发生冲突的,所以不会对资源上锁。 实现方式:当线程要对资源进行更新时,它会先获取资源的版本号或者标识符,并在执行更新…

C++ 视觉开发 六.特征值匹配

以图片识别匹配的案例来分析特征值检测与匹配方法。 目录 一.感知哈希算法(Perceptual Hash Algorithm) 二.特征值检测步骤 1.减小尺寸 2.简化色彩 3.计算像素点均值 4.构造感知哈希位信息 5.构造一维感知哈希值 三.实现程序 1.感知哈希值计算函数 2.计算距离函数 3…

SCI二区TOP|麋鹿群优化算法: 一种新颖的受自然启发的元启发式算法

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年,SO Oladejo受到麋鹿群的繁殖过程启发,提出了麋鹿群优化算法(Elk herd optimizer, EHO)。 2.算法原理 2.1算法思想 EHO灵感来自麋鹿…

vue2 data内对象引用另一个data对象无法使用this的解决办法

背景:data内有一复杂对象,并且内部一属性经常修改,每次修改的话属性.属性会很长,所以希望引用另一简单对象,但data内this用不了。(集合数组是地址引用,基本数据类型这么操作没意义) 如: 解决办法…

字节码编程javassist之定义方法和返回值

写在前面 源码 。 本文看下如何使用javassist来定义方法和返回值。 1:源码 package com.dahuyou.javassist.generateFieldAndMethod;import javassist.*;import java.lang.reflect.Method;public class JustDoIt222 {public static void main(String[] args) thr…

数值分析笔记(五)线性方程组解法

三角分解法 A的杜利特分解公式如下: u 1 j a 1 j ( j 1 , 2 , ⋯ , n ) , l i 1 a i 1 / u 11 ( i 2 , 3 , ⋯ , n ) , u k j a k j − ∑ m 1 k − 1 l b m u m j ⇒ a k j ( j k , k 1 , ⋯ , n ) , l i k ( a i k − ∑ m 1 k − 1 l i n u m k ) /…

苹果电脑能玩赛博朋克2077吗 如何在mac上运行赛博朋克2077 crossover能玩什么游戏

各位喜欢赛博朋克风的一定不能错过《赛博朋克2077》。那么《赛博朋克2077》是一款什么样的游戏?《赛博朋克2077》在苹果电脑上可以运行吗?一起来看看介绍吧。 一、《赛博朋克2077》是一款什么样的游戏? 《赛博朋克2077》是一款由CD Projekt …

[激光原理与应用-98]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 2 - 什么是激光器焊接? 常见的激光焊接技术详解

目录 一、什么是激光焊接 1.1 概述 1.2 激光焊接的优点 二、激光焊接的应用 2.1 哪些场合必须使用激光焊接 1. 汽车制造业 2. 航空航天领域 3. 电子行业:消费类电子3C 4. 医疗器械制造 5. 新能源锂电池行业 6. 其他领域 三、激光焊接的分类 3.1 按焊接…