03-JAVA设计模式-代理模式详解

代理模式

什么是代理模式

Java代理模式是一种常用的设计模式,主要用于在不修改现有类代码的情况下,为该类添加一些新的功能或行为。代理模式涉及到一个代理类和一个被代理类(也称为目标对象)。代理类负责控制对目标对象的访问,并可以在访问前后添加一些额外的操作。

核心作用:

  • 通过代理,控制对对象的访问。
  • 可以详细控制访问某个(某类)对象的方法,在调用这个方法茜做前置处理,调用这个方法后做后置处理(即AOP的微观实现)。

核心角色:

  • 抽象角色:定义代理角色和真实角色的公共对外方法
  • 真实角色:实现抽象角色,定义真实角色所需要实现的业务逻辑,供代理角色调用(关注真正的业务逻辑)。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色业务逻辑方法,来实现抽象角色,并可附加自己的操作。

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟代理:先加载轻量级代理对象,真正需要再加载真实对象。

Java代理模式主要分为两种:静态代理和动态代理。

静态代理

静态代理是在代码中手动定义代理类,代理类与目标对象实现相同的接口,并在代理类中持有目标对象的引用。
当调用代理类的方法时,代理类会在调用目标对象方法的前后添加一些额外的逻辑。

案例

明星个人接活,与代理人接活
明星个人接活:

  1. 面谈
  2. 合同起草
  3. 签字
  4. 前期准备
  5. 唱歌
  6. 收尾款

通过代理人接活:

  1. 面谈
  2. 合同起草
  3. 签字
  4. 前期准备
  5. 唱歌(明星负责)
  6. 收尾款

UML

在这里插入图片描述

1 提供一个抽象角色,定义代理角色和真实角色的公共对外方法。
2 定义真实角色,实现抽象角色的方法(当然真实角色也可以定义一些自己的方法)
3 定义一个代理角色,实现抽象角色方法,通过传入真实角色调用真实角色的方法
4 使用时,创建代理角色及真实角色,通过代理角色调用相应方法

实现代码

Start.java

// 抽象角色
// 明星:具备唱歌能力
public interface Start {
    void sing();
}

RealStart.java

// 真实角色
// * 实现明星唱歌
public class RealStart implements Start{
    @Override
    public void sing() {
        System.out.println("周杰伦唱歌");
    }
}

ProxyStart.java

// 代理明星
// * 通过实现Star方法代理明星唱歌
public class ProxyStart implements Start{
    // 代理明星
    private Start start;

    // 通过构造器传入设置代理明星
    public ProxyStart(Start start) {
        this.start = start;
    }

    // 重写唱歌,代理实现唱歌
    @Override
    public void sing() {
        System.out.print("5. ");
        start.sing();
    }

    public void interview(){
        System.out.println("1. 面谈");
    }

    public void contractDrafting(){
        System.out.println("2. 合同起草");
    }

    public void signature(){
        System.out.println("3. 签字");
    }

    public void preliminaryPreparation(){
        System.out.println("4. 前期准备");
    }

    public void closingPayment(){
        System.out.println("6. 收尾款");
    }
}

TestClient.java

public class TestClient {
    public static void main(String[] args) {
        // 创建真实角色
        RealStart realStart = new RealStart();
        // 创建代理角色
        ProxyStart proxyStart = new ProxyStart(realStart);
        // 方法调用
        // 面谈
        proxyStart.interview();
        // 合同起草
        proxyStart.contractDrafting();
        // 签字
        proxyStart.signature();
        // 前期准备
        proxyStart.preliminaryPreparation();
        // 唱歌
        proxyStart.sing();
        // 收尾款
        proxyStart.closingPayment();
    }
}

执行结果:

在这里插入图片描述

JDK实现动态代理

动态代理是在运行时动态生成代理类。相对于静态代理,将抽象角色(接口)中声明的所有方法都被转移到调用处理器一个集中的地方中处理,这样可以更加灵活和统一的处理众多的方法。

通过JDK提供了java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现动态代理。

  • java.lang.reflect.Proxy
    作用:动态生成代理类和对象
  • java.lang.reflect.InvocationHandler(处理器接口)
    • 可以通过invoke方法实现对真实角色的代理访问。
    • 每次通过Proxy生成代理类对象时都要指定对应的处理器对象。

UML

在这里插入图片描述

使用JD实现动态代理时,我们需要创建一个实现了InvocationHandler接口的处理器类,并在该类中实现invoke方法。
然后,我们可以使用Proxy.newProxyInstance方法创建一个代理类的实例。
当调用代理类的方法时,实际上会调用处理器类的invoke方法。

代码实现

Start.java

// 抽象角色
interface Start {
    void sing();
}

RealStart.java

// 真实角色
// * 实现明星唱歌
public class RealStart implements Start {
    @Override
    public void sing() {
        System.out.println("周杰伦唱歌");
    }
}

StartHandle.java

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

// JDK 实现动态代理
public class StartHandle implements InvocationHandler {
    // 代理明星
    private Start start;
    // 通过构造器传入设置代理明星
    public StartHandle(Start start) {
        this.start = start;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理方法执行之前");
        System.out.println("1. 面谈");
        System.out.println("2. 合同起草");
        System.out.println("3. 签字");
        System.out.println("4. 前期准备");
        System.out.print("5. ");
        method.invoke(start,args);
        System.out.println("代理方法执行之后");
        System.out.println("6. 收尾款");
        return true;
    }
}

TestClient.java

import java.lang.reflect.Proxy;

// 动态代理测试
public class TestClient {
    public static void main(String[] args) {
        // 创建真实角色
        RealStart realStart = new RealStart();
        // 创建动态代理处理类
        StartHandle startHandle = new StartHandle(realStart);
        Start proxy = (Start) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Start.class}, startHandle);
        // 处理业务
        proxy.sing();
    }
}

执行结果:

在这里插入图片描述

通过接口实现代理

UML

在这里插入图片描述

1 定义一个抽象角色Start,接口中定义抽象sing方法

2 真实角色实现Star接口,实现sing方法

3 定义一个中转处理接口MethodHandler,接口中定义invoke方法(后续处理,后续代理类方法全部先调用该方法进行处理)

4 定义处理类(具体实现)StartMethodHandle,实现invoke方法,通过反射调用真实角色对应方法

5 代理对象StartProxy,实现Start接口,重写接口,所有接口调用处理类(具体实现)的invoke方法,并传入真实角色

注意:JDK 实现代理的逻辑与其类似

具体实现

Start.java

// 抽象角色
// * 明星:具备唱歌能力
public interface Start {
    void sing() throws Throwable;
}

RealStart.java

// 真实角色
// * 实现明星唱歌
public class RealStart implements Start {
    @Override
    public void sing() throws Throwable {
        System.out.println("周杰伦唱歌");
    }
}

MethodHandler.java

// 中转处理接口
public interface MethodHandler {
    Object invoke(Object self, String methodName, Object[] args) throws Throwable;
}

StartMethodHandle.java

import java.lang.reflect.Method;
// 处理类(具体实现)
public class StartMethodHandle implements MethodHandler {
    @Override
    public Object invoke(Object self, String methodName, Object[] args) throws Throwable {
        System.out.println("代理执行之前");
        Method method = self.getClass().getMethod(methodName);
        Object invoke = method.invoke(self, args);
        System.out.println("代理执行之后");
        return invoke;
    }
}

StartProxy.java

// 代理对象
public class StartProxy implements Start {
    private MethodHandler methodHandler;
    private Start start;
    public StartProxy(MethodHandler methodHandler, Start start) {
        this.methodHandler = methodHandler;
        this.start = start;
    }
    @Override
    public void sing() throws Throwable {
        methodHandler.invoke(start,"sing",null);
    }
}

TestClient3.java

public class TestClient3 {
    public static void main(String[] args) {
        // 创建真实角色
        Start start = new RealStart();
        // 创建代理处理类
        StartMethodHandle startMethodHandle = new StartMethodHandle();
        // 创建代理对象
        StartProxy startProxy = new StartProxy(startMethodHandle, start);
        try {
            startProxy.sing();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

执行结果:

在这里插入图片描述

javassist字节码实现动态代理

Javassist 是一个开源的分析、编辑和创建 Java 字节码的库。它主要用于操作 Java 字节码,但也可以用来创建动态代理。

需要引入依赖如下

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>

实现代码

Start.java

// 抽象角色
// * 明星:具备唱歌能力
public interface Start {
    void sing();
}

RealStart.java

/**
 * 真实角色
 * 实现明星唱歌
 *
 * @author Anna.
 * @date 2024/4/9 10:10
 */
public class RealStart implements Start {
    @Override
    public void sing() {
        System.out.println("周杰伦唱歌");
    }
}

MethodHandler.java

// 中转处理接口
public interface MethodHandler {
    Object invoke(Object self, String methodName, Object[] args) throws Exception;
}

StartMethodHandle.java

// 处理类
public class StartMethodHandle implements MethodHandler {
    @Override
    public Object invoke(Object self, String methodName, Object[] args) throws Exception {
        System.out.println("代理执行之前");
        Method method = self.getClass().getMethod(methodName);
        Object invoke = method.invoke(self, args);
        System.out.println("代理执行之后");
        return invoke;
    }
}

JavassistProxyFactory.java

import javassist.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class JavassistProxyFactory {

    /**
     * 创建代理类:相当于实现demo3中StartProxy
     *
     * @param target   真实角色
     * @param methodHandler  处理类(具体实现)
     * @return T
     * @author Anna.
     * @date 2024/4/9 17:26
     */
    public static <T> T createProxy(T target, MethodHandler methodHandler) throws Exception {
        // 创建 ClassPool,它是 Javassist 的核心类,用于处理类
        ClassPool pool = ClassPool.getDefault();
        // 获取目标类的 CtClass 对象
        CtClass ctClass = pool.get(target.getClass().getName());
        // 获取目标类的 CtClass 对象
        CtClass methodHandlerCtClass = pool.get(methodHandler.getClass().getName());
        // 创建代理类的名称
        String proxyClassName = target.getClass().getName() + "$Proxy";
        // 创建代理类的 CtClass 对象
        CtClass proxyCtClass = pool.makeClass(proxyClassName);
        // 设置代理类继承自目标类 用于实现其所有方法
        proxyCtClass.setSuperclass(ctClass);

        // 添加字段
        CtField aField = new CtField(ctClass, "a", proxyCtClass);
        aField.setModifiers(javassist.Modifier.PRIVATE);
        CtField bField = new CtField(methodHandlerCtClass, "b", proxyCtClass);
        bField.setModifiers(javassist.Modifier.PRIVATE);

        // 添加属性
        proxyCtClass.addField(aField);
        proxyCtClass.addField(bField);

        // 创建一个构造函数来初始化name属性
        CtConstructor constructor = new CtConstructor(new CtClass[]{ctClass,methodHandlerCtClass}, proxyCtClass);
        constructor.setModifiers(javassist.Modifier.PUBLIC);
        // $0 this $1 第一个参数 $2 第二个参数 依次类推
        constructor.setBody("{this.a = $1;this.b = $2;}");
        proxyCtClass.addConstructor(constructor);

        // 遍历目标类的所有方法,并创建相应的方法在代理类中
        for (Method method : target.getClass().getDeclaredMethods()) {
            // 创建方法
            CtMethod ctMethod = new CtMethod(pool.get(method.getReturnType().getName()), method.getName(), getParameterTypes(method), proxyCtClass);
            ctMethod.setModifiers(Modifier.PUBLIC);
            // 设置方法体,调用 MethodHandler 的 invoke 方法
            ctMethod.setBody("{ b.invoke(a, \"" + method.getName() + "\", $args); }");
            // 添加方法到代理类
            proxyCtClass.addMethod(ctMethod);
        }

        // 反射
        Class<?>  aClass = proxyCtClass.toClass();
        Constructor<?>[] constructors = aClass.getConstructors();
        Object obj = constructors[0].newInstance(target, methodHandler);
        // 创建代理类的实例
        return (T) obj;
    }

    /**
     * 获取方法参数列表
     *
     * @param method
     * @return javassist.CtClass[]
     * @author Anna.
     * @date 2024/4/9 17:40
     */
    private static CtClass[] getParameterTypes(Method method) throws NotFoundException {
        CtClass[] ctClasses = new CtClass[method.getParameterTypes().length];
        for (int i = 0; i < method.getParameterTypes().length; i++) {
            ctClasses[i] = ClassPool.getDefault().get(method.getParameterTypes()[i].getName());
        }
        return ctClasses;
    }

}

TestClient4.java

public class TestClient4 {
    public static void main(String[] args) throws Exception {
        Start realStart = new RealStart();
        StartMethodHandle startMethodHandle = new StartMethodHandle();
        Start proxy = (Start) JavassistProxyFactory.createProxy(realStart, startMethodHandle);
        proxy.sing();
    }
}

执行结果:

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

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

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

相关文章

Github 2024-04-09 Python开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-09统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10Vue项目1JavaScript项目1系统设计指南 创建周期:2507 天开发语言:Python协议类型:OtherStar数量:241693 个Fork数量:42010 次…

贪心算法|860.柠檬水找零

力扣题目链接 class Solution { public:bool lemonadeChange(vector<int>& bills) {int five 0, ten 0, twenty 0;for (int bill : bills) {// 情况一if (bill 5) five;// 情况二if (bill 10) {if (five < 0) return false;ten;five--;}// 情况三if (bill …

SpringBoot快速入门笔记(6)

文章目录 Axios网络请求1、简介2、导入3、网络请求4、跨域问题5、数据渲染6、全局配置 Axios网络请求 1、简介 项目开发中&#xff0c;前端页面需要的数据往往要从服务器端获取&#xff0c;这必然涉及到和服务器的通信 Axios基于promise网络请求库&#xff0c;作用于node.js和…

Apache Incubator Answer 本地开发部署

文章目录 简介Github文档插件部署 Answer开发环境编译项目初始化项目运行项目 简介 一款适合任何团队的问答平台软件。 Apache Incubator Answer是一个开源项目&#xff0c;它是一个用于构建和部署问答系统的框架。该项目是Apache软件基金会的孵化器项目&#xff0c;提供一个…

PVE下安装配置openwrt和ikuai

开端 openwrt 和 ikuai 是比较出名的软路由系统。我最早接触软路由还是因为我的一个学长要改自己家里的网络&#xff0c;使用软路由去控制网络。我听说后便来了兴致&#xff0c;也在我家搞了一套软路由系统。现在我已经做完了&#xff0c;就想着写个文章记录一下。 软路由简介…

云数据库价格一瞥(华为云、百度智能云、腾讯云、阿里云)

最近&#xff0c;大家似乎和价格“磕”上了。本文仅考虑主流产品&#xff08; RDS MySQL、Redis &#xff09;的部分主流规格&#xff0c;对各家厂商的价格做一个对比&#xff0c;供参考。 TL;DR&#xff1a; 总体来看&#xff0c;各家云厂商价格趋于持平&#xff0c;部分主流商…

关于阿里云centos系统下宝塔面板部署django/中pip install mysqlclient失败问题的大总结/阿里云使用oss长期访问凭证

python版本3.12.0 问题1 解决方案 sudo vim /etc/profile export MYSQLCLIENT_CFLAGS"-I/usr/include/mysql" export MYSQLCLIENT_LDFLAGS"-L/usr/lib64/mysql" Esc退出编辑模式 &#xff1a;wq退出并且保存 问题二 说是找不到 mysql.h头文件 CentOS ‘…

【数据结构】考研真题攻克与重点知识点剖析 - 第 8 篇:排序

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

Python单元测试pytest捕获日志输出

使用pytest进行单元测试时&#xff0c;遇到了需要测试日志输出的情况&#xff0c;查看了文档 https://docs.pytest.org/en/latest/how-to/capture-stdout-stderr.html https://docs.pytest.org/en/latest/how-to/logging.html 然后试了一下&#xff0c;捕捉logger.info可以用…

【Qt】网络

目录 一、Udp Socket 二、Tcp Socket 三、HTTP 在进行网络编程之前&#xff0c;需要在项目中的 .pro 文件中添加 network 模块 有时添加之后要手动编译一下项目&#xff0c;使 Qt Creator 能够加载对应模块的头文件 一、Udp Socket 主要的类有两个&#xff1a;QUdpSocket …

Octopus:2B 参数语言模型即可媲美 GPT-4 的函数调用性能

近年来&#xff0c;大语言模型在 PC、智能手机和可穿戴设备的操作系统中应用逐渐成为趋势。 例如&#xff0c;MultiOn (Garg, 2024) 和 Adept AI (Luan, 2024) 等 AI 助理工具&#xff0c;以及 Rabbit R1 (Lyu, 2024) 和 Humane AI Pin (Chaudhri, 2024) 等 AI 消费产品在消费者…

Mac 装 虚拟机 vmware、centos7等,21年网络安全面经分享

链接: https://pan.baidu.com/s/1oZw1cLyl6Uo3lAD2_FqfEw?pwdzjt4 提取码: zjt4 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 centos8 链接: https://pan.baidu.com/s/10KWpCUa2JkwcjYlJZVogKQ?pwdn99a 提取码: n99a 复制这段内容后打开百度网盘手机App&…

电脑总是蓝屏怎么办,电脑总是蓝屏怎么办开不了机

工作离不开电脑&#xff0c;不过电脑经常会出现一些故障让我们崩溃不已&#xff0c;尤其类似电脑蓝屏开不了机这种&#xff0c;总是突然发生&#xff0c;让人猝不及防。那么面对这一情况&#xff0c;相信很多人都是不知道该如何处理的。这里提供两个方法&#xff0c;第一种方法…

网络安全---RSA公钥加密与签名

实验项目&#xff1a;RSA公钥加密与签名实验 1.实验目的 本实验的学习目标是让学生获得 RSA 算法的动手经验。 通过课堂学习&#xff0c;学生应该已经了解 RSA 算法的理论部分&#xff0c; 知道在数学上如何生成公钥、私钥以及如何执行加密、解密和签名生成、验证。 通过使用…

防SSL证书泄露服务器IP教程

在Web CDN&#xff08;内容分发网络&#xff09;中&#xff0c;防止SSL泄露源服务器IP是一个重要的安全考虑。下面是一些建议的方法来实现这一目标&#xff1a; 首先呢&#xff0c;我们隐藏服务器IP不要使用服务器IP生成的SSL证书&#xff0c;不然会泄露我们的服务器IP。 泄露了…

【BEV 视图变换】Fast-Ray 基于查找表LUT、多视角到单个三维体素转换

前言 在BEV感知方案中&#xff0c;将图像特征转为BEV特征&#xff0c;是关键的一步&#xff0c;这过程也称为2D视图变换。 本文介绍Fast-Ray方法&#xff0c;在Fast-BEV中被提出的&#xff0c;它是一种轻量级并且易于部署的视图转换方法&#xff0c;用于快速推理。 通过将多…

.net 6 集成NLog

.net 6 webapi项目集成NLog 上代码step 1 添加nugetstep 2 添加支持step 3 添加配置文件 结束 上代码 step 1 添加nuget 添加nuget 包 Roc step 2 添加支持 修改program.cs var builder WebApplication.CreateBuilder(args); // 添加NLog日志支持 builder.AddRocNLog();ste…

java中static关键字(尚未完善)

文章目录 static关键字static可修饰static方法举例static代码块拓展其他链接 static关键字 加载顺序类是构建对象的模板&#xff0c;一个类多个对象static修饰的方法或者变量都属于类&#xff0c;类独有的 static可修饰 修饰变量&#xff08;属于类变量&#xff0c;被创建出来…

极狐GitLab 如何在 helm 中恢复数据

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何在极狐GitLab …

mysql运维知识总结

1. 日志 1.1 错误日志 错误日志是 MySQL 中最重要的日志之一&#xff0c;它记录了当 mysqld 启动和停止时&#xff0c;以及服务器在运行过 程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&…