代理模式的实现

news2024/10/5 13:00:24

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.coloradmin.cn/o/1900828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

优雅谈大模型:揭开计算机视觉任务神秘面纱

人工智能在第四次工业革命发挥着至关重要的作用,它广泛的融入日常生活,例如Google助手、Siri、智能手机摄像头、社交媒体过滤器、自动标记、医疗成像、导航等,所有这些技术都切实的改进和增强日常活动的便利性和习惯。 大模型技术发展到现在…

基于Java技术的篮球论坛系统

你好呀,我是计算机学姐码农小野!如果有相关需求,可以私信联系我。 开发语言 Java 数据库 MySQL 技术 B/S模式、Java技术 工具 Visual Studio、MySQL数据库开发工具 系统展示 首页 用户注册界面 篮球论坛界面 个人中心界面 摘要 本…

AI助手崛起:开发者的新伙伴还是未来替代者?

你好,我是三桥君。 自从 ChatGPT 问市以来,AI 将取代开发者的声音不绝于耳,至今还是互联网异常火热的问题。 在软件开发领域,生成式人工智能(AIGC)正在改变开发者的工作方式。无论是代码生成、错误检测还是…

【mybatis】mybatisX插件概述

一、主要功能 智能补全与提示 MyBatisX 可以智能地提示和补全 SQL 语句中的关键字、表名、列名等信息,从而显著提高开发效率。代码生成器 虽然 MyBatisX 本身可能不直接提供一个完整的、独立的代码生成器,但它可能集成了或支持与其他代码生成工具&#…

C语言_练习题

求最小公倍数 思路:假设两个数,5和7,那么最小至少也要7吧,所以先假定最小公倍数是两个数之间较大的,然后看7能不能同时整除5和7,不能就加1继续除 int GetLCM(int _num1, int _num2) {int max _num1>_n…

异步主从复制

主从复制的概念 主从复制是一种在数据库系统中常用的数据备份和读取扩展技术,通过将一个数据库服务器(主服务器)上的数据变更自动同步到一个或多个数据库服务器(从服务器)上,以此来实现数据的冗余备份、读…

【CUDA】 扫描 Scan

Scan Scan操作是许多应用程序中常见的操作。扫描操作采用一个二元运算符⊕和一个输入数组并计算输出数组如下: [x0,(x0⊕x1),…,( x0⊕x1⊕…..⊕xn-1)] 分层扫描和多种Scan算法介绍 Kogge-Stones Algorithm Kogge-Stones Algorithm最初是为设计快速加法电路而发…

Android Graphics 显示系统 - 监测、计算FPS的工具及设计分析

“ 在Android图像显示相关的开发、调试、测试过程中,如何能有效地评估画面的流畅度及监测、计算图层渲染显示的实时FPS呢?本篇文章将会提供一种实用、灵巧的思路。” 01 设计初衷 面对开发测试中遇到的卡顿掉帧问题,如何在复现卡顿的过程中持…

黑马的ES课程中的不足

在我自己做项目使用ES的时候,发现了黑马没教的方法,以及一些它项目的小问题 搜索时的匹配方法 这个boolQuery().should 我的项目是通过文章的标题title和内容content来进行搜索 但是黑马它的项目只用了must 如果我们的title和content都用must&#x…

QCustomPlot+ vs2022+ qt

零、printSupport 步骤一:下载QCustomPlot 访问QCustomPlot的官网 QCustomPlot 下载最新版本的源代码。 步骤二:配置项目 创建新的Qt项目: 打开VS2022,创建一个新的Qt Widgets Application项目。 将QCustomPlot源代码添加到项目…

C语言编程与进阶

1.0 C语言关键字 1-1C语言关键字-CSDN博客文章浏览阅读831次,点赞13次,收藏24次。define使用define定义常量return 0;使用define定义宏// define 定义宏,名字是ADD(x,y),x y 是宏的参数int a 10;int b 20;return 0;宏定义的本质是替换&am…

JavaEE——计算机工作原理

冯诺依曼体系(VonNeumannArchitecture) 现代计算机,大多遵守冯诺依曼体系结构 CPU中央处理器:进行算术运算与逻辑判断 存储器:分为外存和内存,用于存储数据(使用二进制存储) 输入…

百日筑基第十二天-入门Elasticsearch

百日筑基第十二天-入门Elasticsearch Elasticsearch 是什么 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎。 安装 Elasticsearch 下载:https://www.elastic.co/cn/downloads/elasticsearch Elasticsearch 是免安装的,只需要把 zip…

绝了,华为伸缩摄像头如何突破影像边界?

自华为Pura70 Ultra超聚光伸缩镜头诞生以来,备受大家的关注,听说这颗镜头打破了传统手机的摄像头体积与镜头的设计,为我们带来了不一样的拍照体验。 智能手机飞速发展的今天,影像功能已经成为我们衡量一款手机性能的重要指标。想…

【Qt5.12.9】程序无法显示照片问题(已解决)

问题记录:Qt5.12.9下无法显示照片 我的工程名为03_qpainter,照片cd.png存放在工程目录下的image文件夹中。 /03_qpainter/image/cd.png 因为这是正点原子Linux下Qt书籍中的例程,在通过学习其配套的例程中的项目,发现我的项目少…

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

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

昇思25天学习打卡营第1天|初识MindSpore

# 打卡 day1 目录 # 打卡 day1 初识MindSpore 昇思 MindSpore 是什么? 昇思 MindSpore 优势|特点 昇思 MindSpore 不足 官方生态学习地址 初识MindSpore 昇思 MindSpore 是什么? 昇思MindSpore 是全场景深度学习架构,为开发者提供了全…

Ubuntu固定虚拟机的ip地址

1、由于虚拟机网络是桥接,所以ip地址会不停地变化,接下来我们就讲述ip如何固定 2、如果apt安装时报错W: Target CNF (multiverse/cnf/Commands-all) is configured multiple times in /etc/apt/sources.list:10, 检查 /etc/apt/sources.list…

计算机组成原理--概述

🌈个人主页:小新_- 🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 🏆所属专栏&#xff1…

AI Earth应用—— 在线使用sentinel数据VV和VH波段进行水体提取分析(昆明抚仙湖、滇池为例)

AI Earth 本文的主要目的就是对水体进行提取,这里,具体的操作步骤很简单基本上是通过,首页的数据检索,选择需要研究的区域,然后选择工具箱种的水体提取分析即可,剩下的就交给阿里云去处理,结果如下: 这是我所选取的一景影像: 详情 卫星: Sentinel-1 级别: 1 …