Spring(七)AOP-代理模式

目录

代理模式

一 静态代理

一、核心作用

二、使用场景

二 动态代理

一、核心作用

二、使用场景

具体实现:(初始)

具体实现:(改进)

一、核心业务逻辑

1. 接口 MathCalculator

2. 实现类 MathCalculatorImpl

二、日志工具模块

日志工具类 logUtils

三、动态代理模块

动态代理类 DynamicProxy

四、测试验证模块

测试方法 test03()


概念:AOP 面向切面编程(Aspect-Oriented Programming)

面向切面编程,能够将那些与业务无关,却为业务模块所调用的逻辑封装起来,以减少系统代码的重复度,减少模块之间的耦合度。


代理模式

对比维度静态代理动态代理
代理关系确定时机编码期间:代理类需手动编写,明确代理目标运行期间:代理类由程序动态生成,无需预定义
实现方式代理类与目标类实现相同接口(或继承目标类),并通过组合持有目标对象通过反射(JDK动态代理)或字节码生成(CGLIB)动态拦截目标对象方法
代理范围- 仅能代理同一接口/父类的类
- 需为每个目标类单独编写代理类
JDK动态代理:代理接口实现类
CGLIB:代理无接口类(非final类)
代码复杂度- 代码简单直观,适合新手
- 需手动维护代理类
- 需理解反射/字节码机制
- 框架(如Spring)可简化开发
性能- 直接调用目标方法,无反射开销
- 性能更高
JDK动态代理:反射调用性能较低
CGLIB:首次生成代理类较慢
灵活性- 扩展功能固定,仅针对特定类
- 接口变动需同步修改代理类
- 可批量代理多个类,统一添加功能(如日志、事务)
- 接口变动自动适配
典型应用场景- 代理少量固定类
- 高频调用场景(如核心服务)
- 框架级功能扩展(Spring AOP)
- 需统一管理多个类的增强逻辑

静态代理:编码期间就决定好了代理关系

定义:代理对象是目标对象的子类型,代理对象本省并不是目标对象,而是将目标作为自己的属性

优点:同一种类型的所有对象都能代理

缺点:范围太小

动态代理:运行期间才决定好了代理关系

定义:目标对象会在执行期间被动态拦截,插入指定逻辑

优点:可以代理世间万物

缺点:复杂不好写

一 静态代理

静态代理是通过手动编写一个“替身类”(代理类),在不修改原始类代码的前提下,为其添加额外功能(如日志、权限校验),使用时通过代理类间接调用原始类。

一、核心作用

  1. 控制访问:限制或增强对原始对象的访问(如权限校验)。

  2. 功能扩展:在不修改原始对象代码的前提下,添加新功能(如日志记录、性能统计)。


二、使用场景

  1. 日志记录:在方法调用前后自动记录日志。

  2. 权限校验:调用方法前检查用户权限。

  3. 延迟加载:在需要时才初始化复杂对象(如大文件加载)。

  4. 简化复杂操作:隐藏底层复杂逻辑,对外提供简洁接口。

  5. 避坑提示:如果系统中有大量需要代理的类,建议改用动态代理(如JDK动态代理或CGLIB)。

代码实现:

定义接口:

package org.example.spring02.MathMethod;public interface MathCalculator {//定义四则运算public int div(int a, int b);public int mul(int a, int b);public int sub(int a, int b);public int add(int a, int b);}

实现类:
 

package org.example.spring02.MathMethod.MIm;import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;@Component
public class MathCalculatorImpl implements MathCalculator {@Overridepublic int add(int a, int b) {return a + b;}@Overridepublic int sub(int a, int b) {return a - b;}@Overridepublic int mul(int a, int b) {return a * b;}@Overridepublic int div(int a, int b) {return a / b;}}

静态代理类:

package org.example.spring02.MathMethod.proxy.statics;import lombok.Data;
import org.example.spring02.MathMethod.MathCalculator;/*** 静态代理类,实现了MathCalculator接口* 本类用于在不修改原始类的情况下,增加额外的功能处理,如日志、权限校验等
*/@Data
public class CalculatorStaticProxy implements MathCalculator {// 被代理对象private MathCalculator target;public CalculatorStaticProxy(MathCalculator mc) {this.target = mc;}@Overridepublic int div(int a, int b) {//书写日志System.out.println("div被调用了,参数是:" + a + "," + b);System.out.println("div被调用了,结果是:" + target.div(a, b));return target.div(a, b);}@Overridepublic int mul(int a, int b) {System.out.println("mul被调用了,参数是:" + a + "," + b);System.out.println("mul被调用了,结果是:" + target.mul(a, b));return target.mul(a, b);}@Overridepublic int sub(int a, int b) {System.out.println("sub被调用了,参数是:" + a + "," + b);System.out.println("sub被调用了,结果是:" + target.sub(a, b));return target.sub(a, b);}@Overridepublic int add(int a, int b) {System.out.println("add被调用了,参数是:" + a + "," + b);System.out.println("add被调用了,结果是:" + target.add(a, b));return target.add(a, b);}
}

测试类:

    // JUnit测试方法,用于测试MathCalculator的功能@Testvoid test01() {// 创建MathCalculator的实例对象MathCalculator target = new MathCalculatorImpl();// 调用add方法进行加法运算,并打印结果int add1 = target.add(1, 3);System.out.println(add1);// 分隔符,用于区分不同的测试部分System.out.println("======");// 创建静态代理对象,并传入目标对象CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(target);// 通过静态代理对象调用add方法进行加法运算,并打印结果int add = calculatorStaticProxy.add(1, 3);System.out.println(add);}

运行展示:

二 动态代理

动态代理:JDK动态代理目标对象必须有接口,代理的也只是接口规定的方法

动态代理:是在运行时通过反射或字节码技术自动生成代理类,无需手动编写“替身类”,即可为多个目标对象统一添加功能(如日志、事务管理)。

拦截处理:动态代理对象在调用原生对象的方法时,通过统一的入口(如 InvocationHandler.invoke())拦截方法调用,从而实现对参数、方法执行过程和返回结果的增强或修改

一、核心作用

  1. 统一扩展功能:批量对多个类添加相同的增强逻辑。

  2. 解耦代码:将核心业务逻辑与辅助功能(如权限校验)分离。

  3. 动态适配:运行时根据需求灵活生成代理对象。


二、使用场景

  1. 框架级功能扩展:如Spring AOP(日志、事务、权限)。

  2. RPC调用:隐藏网络通信细节,让远程调用像本地调用。

  3. 接口监控:统计接口调用耗时、成功率等。

  4. Mock测试:动态生成测试用的代理对象。

1. ClassLoader loader

  • 作用
    动态代理生成的字节码需要被类加载器加载到 JVM 中。使用目标对象的类加载器可确保代理类能访问目标类及其接口。

  • 示例

// 目标对象
UserService target = new UserServiceImpl();
// 使用目标对象的类加载器
ClassLoader loader = target.getClass().getClassLoader();

2.Class<?>[] interfaces

  • 作用
    指定代理类需要实现的接口。代理对象会实现这些接口的所有方法,并将方法调用委托给 InvocationHandler

  • 示例

// 目标对象实现的接口
Class<?>[] interfaces = target.getClass().getInterfaces();

3.InvocationHandler h

  • 作用
    定义代理逻辑的核心处理器。所有通过代理对象调用的方法,都会触发 invoke() 方法,开发者可在此插入增强逻辑。

  • 示例

InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 增强逻辑(如记录日志)System.out.println("调用方法: " + method.getName());// 调用目标对象的方法return method.invoke(target, args);}
};

具体实现:(初始)

    @Testvoid test02() {// 原生对象MathCalculator target = new MathCalculatorImpl();int add1 = target.add(1, 3);System.out.println("原生对象");System.out.println(add1);//创建动态代理/*三个参数proxy:代理对象->相当于明星的代理人method:代理对象调用目标对象的方法args:方法调用传递的参数*/InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 调用目标对象的方法并返回System.out.println("动态代理InvocationHandler的invoke中定义的");System.out.println("[日志] 调用了方法: " + method.getName());System.out.println("[日志] 获得参数: " + Arrays.toString(args));return method.invoke(target, args);}};/*三个参数ClassLoader:确保代理类的正确加载。Interfaces:定义代理类的行为范围(基于接口)。InvocationHandler:实现动态代理的核心逻辑(AOP 的基础)。*/MathCalculator proxy = (MathCalculator) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),invocationHandler);int add = proxy.add(1, 3);System.out.println(add);}

具体实现:(改进)

一、核心业务逻辑

1. 接口 MathCalculator
  • 作用:定义业务契约,明确四则运算的规范。

  • 关键点

    • 声明了加减乘除四个方法。

    • 作为动态代理的接口约束(JDK动态代理必须基于接口)。

2. 实现类 MathCalculatorImpl
  • 作用:具体实现四则运算的业务逻辑。

  • 关键点

    • 使用 @Component 标注为 Spring 组件(若配合 Spring 使用)。

    • 实现了 MathCalculator 接口中的方法。

    • 是动态代理的目标对象(原生对象)。

接口

package org.example.spring02.MathMethod;public interface MathCalculator {//定义四则运算public int div(int a, int b);public int mul(int a, int b);public int sub(int a, int b);public int add(int a, int b);}

实现类

package org.example.spring02.MathMethod.MIm;import org.example.spring02.MathMethod.MathCalculator;
import org.springframework.stereotype.Component;@Component
public class MathCalculatorImpl implements MathCalculator {@Overridepublic int add(int a, int b) {return a + b;}@Overridepublic int sub(int a, int b) {return a - b;}@Overridepublic int mul(int a, int b) {return a * b;}@Overridepublic int div(int a, int b) {return a / b;}}

二、日志工具模块

日志工具类 logUtils
  • 作用:提供静态方法统一管理日志格式。

  • 核心方法

    • logStart():记录方法开始执行的日志(含方法名和参数)。

    • logEnd():记录方法正常结束的日志(含结果)。

    • logException():记录方法执行异常的日志。

  • 设计亮点

    • 使用 Object... args 支持可变参数,适配不同方法的参数。

    • 通过 Arrays.toString(args) 格式化参数输出。

package org.example.spring02.log;import java.util.Arrays;public class logUtils {public static void logStart(String name , Object... args){System.out.println("开始执行方法:"+name+",参数:"+ Arrays.toString(args));}public static void logEnd(String name , Object result){System.out.println("方法:"+name+",执行结果:"+result);}public static void logException(String name , Exception e){System.out.println("方法:"+name+",执行异常:"+e.getMessage());}public static void logEnd(){System.out.println("方法执行结束");}}

三、动态代理模块

动态代理类 DynamicProxy
  • 作用:生成代理对象,为原生对象的方法调用添加日志增强逻辑。

package org.example.spring02.MathMethod.proxy.dynamic;import org.example.spring02.log.logUtils;import java.lang.reflect.Proxy;
import java.util.Arrays;public class DynamicProxy {public static  Object newProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy, method, args) -> {System.out.println("动态代理开始");System.out.println("[日志] 调用了方法: " + method.getName());logUtils.logStart(method.getName(), args);System.out.println("[日志] 获得参数: " + Arrays.toString(args));return method.invoke(target, args);});}
}

四、测试验证模块

测试方法 test03()
  • 作用:验证动态代理的日志增强功能是否生效。

    @Testvoid test03(){MathCalculator o = (MathCalculator) DynamicProxy.newProxyInstance(new MathCalculatorImpl());int add = o.add(1, 3);System.out.println(add);}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI革命编程学习:Python语法速通与高阶突破全实战(第二部分:AI辅助调试与高阶编程)

只要编程&#xff0c;代码错误和有问题肯定是难免&#xff0c;更何况还是全AI生成代码&#xff0c;是否符合我们的要求与预期&#xff0c;以及代码是否有逻辑错误&#xff0c;是否有代码错误。下面我们继续这一部分&#xff0c;代码调试、代码修复等基本过程。 一、代码调试 …

脚本学习(1)验证目录自动化生成脚本

1、脚本介绍 旨在一键创建符合IC验证规范的目录结构&#xff0c;避免手动创建目录和文件的重复劳动。 优点&#xff1a;模块级验证目录可一键创建&#xff0c;代码简单易懂&#xff0c;可复用性高。 缺点&#xff1a;子系统或系统级不适用。 2、生成的目录结构 /home/user…

【C#实现手写Ollama服务交互,实现本地模型对话】

前言 C#手写Ollama服务交互&#xff0c;实现本地模型对话 最近使用C#调用OllamaSharpe库实现Ollama本地对话&#xff0c;然后思考着能否自己实现这个功能。经过一番查找&#xff0c;和查看OllamaSharpe源码发现确实可以。其实就是开启Ollama服务后&#xff0c;发送HTTP请求&a…

【十四】Golang 接口

&#x1f4a2;欢迎来到张胤尘的开源技术站 &#x1f4a5;开源如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 接口接口定义接口初始化接口嵌套空接口存储任意类…

Spark(8)配置Hadoop集群环境-使用脚本命令实现集群文件同步

一.hadoop的运行模式 二.scp命令————基本使用 三.scp命令———拓展使用 四.rsync远程同步 五.xsync脚本集群之间的同步 一.hadoop的运行模式 hadoop一共有如下三种运行方式&#xff1a; 1. 本地运行。数据存储在linux本地&#xff0c;测试偶尔用一下。我们上一节课使用…

基于Django的协同过滤算法养老新闻推荐系统的设计与实现

基于Django的协同过滤算法养老新闻推荐系统&#xff08;可改成普通新闻推荐系统使用&#xff09; 开发工具和实现技术 Pycharm&#xff0c;Python&#xff0c;Django框架&#xff0c;mysql8&#xff0c;navicat数据库管理工具&#xff0c;vue&#xff0c;spider爬虫&#xff0…

基于STM32的逻辑分析仪

目录 制约性能因素协议命令下位机回复CMD_ID的回复CMD_METADATA命令的回复上报的采样数 设置使用开源软件PulseView设置操作1&#xff0e;设置采样数2&#xff0e;设置采样频率3.使能或禁止通道4.设置通道的触发条件 实现准备汇编指令精确测量时间 程序C语言初实现采集数据上报…

Kafka,Mq,Redis作为消息队列使用时的差异?|消息队列

在分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff0c;MQ&#xff09;扮演着至关重要的角色&#xff0c;负责解耦系统、削峰填谷、提升系统的吞吐量。Kafka、传统的MQ&#xff08;如RabbitMQ、ActiveMQ&#xff09;和Redis在实际应用中都被广泛用作消息队列…

SpringBoot + vue 管理系统

SpringBoot vue 管理系统 文章目录 SpringBoot vue 管理系统 1、成品效果展示2、项目准备3、项目开发 3.1、部门管理 3.1.1、前端核心代码3.1.2、后端代码实现 3.2、员工管理 3.2.1、前端核心代码3.2.2、后端代码实现 3.3、班级管理 3.3.1、前端核心代码3.3.2、后端代码实现 …

JVM常用概念之本地内存跟踪

问题 Java应用启动或者运行过程中报“内存不足&#xff01;”&#xff0c;我们该怎么办? 基础知识 对于一个在本地机器运行的JVM应用而言&#xff0c;需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构&#xff0c;来保证JVM应用的成功启动以及未来平…

计算机网络基础知识(web漏洞解析与攻防实战)

感谢机械工业出版社赠送的《web漏洞解析与攻防实战》&#xff0c;这本书是2023年刚刚出版的&#xff0c;作者是安全圈子里非常出名的do9gy和phith0n&#xff0c;今日有幸拜读大作&#xff0c;欣喜万分。受益匪浅的一点是&#xff0c;认识到POST请求上传文件的时候&#xff0c;H…

Vue 过滤器 filter(s) 的使用

即过滤器是用来格式化数据的一个函数。过滤器不会修改原始数据&#xff0c;它的作用是过滤数据&#xff0c;就是对数据进行加工处理并返回处理后的数据&#xff0c;比如做一些数据格式上的修改&#xff0c;状态转换等。 过滤器分为两种 组件内的过滤器(组件内有效) 全局过滤器…

cocos creator使用mesh修改图片为圆形,减少使用mask,j减少drawcall,优化性能

cocos creator版本2.4.11 一个mask占用drawcall 3个以上&#xff0c;针对游戏中技能图标&#xff0c;cd,以及多玩家头像&#xff0c;是有很大优化空间 1.上代码&#xff0c;只适合单独图片的&#xff0c;不适合在图集中的图片 const { ccclass, property } cc._decorator;c…

deepseek的regflow安装mac版本

deepseek的ragflow部署安装 一:ollama安装,自行完成,我本地已安装 二:查看大模型情况oll::命令ollama list,我本地无ragflow 三:docker安装:命令docker version ,自行完成,我本地已安装 四:安装知识库软件ragflow: 简单科普下Ragflow 是一个基于深度学习模型的问答生成工具&…

【华三】STP端口角色与状态深度解析

STP端口角色与状态深度解析&#xff1a;构建无环网络的基石 引言一、STP基础回顾二、端口角色详解1. 根端口&#xff08;Root Port&#xff09;2. 指定端口&#xff08;Designated Port&#xff09;3. 非指定端口&#xff08;阻塞端口&#xff09; 三、端口状态转换流程四、角色…

【JavaWeb学习Day23】

Maven高级 分模块设计与开发 分模块设计&#xff1a;将一个大项目分成若干个子模块&#xff0c;方便项目的维护、扩展&#xff0c;也方便模块间的相互引用&#xff0c;资源共享。 策略&#xff1a; 1.策略一&#xff1a;按照功能模块拆分&#xff0c;比如&#xff1a;公共组…

MySQL(社区版)安装过程

1.下载地址 mysql官方网站&#xff1a;www.mysql.com 也可以从Oracle官网进入&#xff1a;https://www.oracle.com/ 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 选择对应版本以及操作系统&#xff0c;MSI是安装包方式&#xff0c;ZIP是压缩包方式。 2.解…

【神经网络】python实现神经网络(二)——正向推理的模拟演练

一.神经网络假设 在开始讲解之前,首先我们假设有这样一套神经网络,一共有三层: 其中,关于神经网络的权重、偏置的符号定义如下(如果不知道什么是权重和偏置,可以参考我之前写过的一篇文章:【机器学习】机器学习是什么意思): 以下文章将沿用以上这个设…

Ubuntu用户安装cpolar内网穿透

前言 Cpolar作为一款体积小巧却功能强大的内网穿透软件&#xff0c;不仅能够在多种环境和应用场景中发挥巨大作用&#xff0c;还能适应多种操作系统&#xff0c;应用最为广泛的Windows、Mac OS系统自不必多说&#xff0c;稍显小众的Linux、树莓派、群辉等也在起支持之列&#…

leetcode 78. 子集(二进制枚举详解)c++

⼆进制枚举 ⼆进制枚举&#xff1a;⽤⼀个数⼆进制表⽰中的 0/1 表⽰两种状态&#xff0c;从⽽达到枚举各种情况。 利⽤⼆进制枚举时&#xff0c;会⽤到⼀些位运算的知识。关于⽤⼆进制中的 0/1 表⽰状态这种⽅法&#xff0c;以后在讨论状态压缩 dp 中会继续使⽤到。 ⼆进制…