实用指南:JavaWeb 课堂笔记 —— 24 AOP 面向切面编程

news/2025/10/14 13:10:56/文章来源:https://www.cnblogs.com/ljbguanli/p/19140876

实用指南:JavaWeb 课堂笔记 —— 24 AOP 面向切面编程

本文介绍了Spring AOP的基本概念与实现方式。

AOP(面向切面编程)通过动态代理机制对特定方法进行编程,解决代码重复性问题。文章以统计方法执行耗时为例,演示了Spring AOP的快速入门步骤:导入依赖、编写切面类、定义切入点表达式。核心概念包括连接点、通知、切入点等,并详细讲解了AOP的执行流程和五种通知类型。此外,还介绍了通知顺序规则、切入点表达式语法(@execution和@annotation)及其通配符使用技巧,提出了书写切入点表达式的优化建议。最后展示了如何通过自定义注解实现更灵活的AOP编程。

01 概述

AOP全称Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

场景:我们的案例部分功能运行较慢,定位耗时较长,需要统计每一个业务方法的执行耗时,旧有的方法通过时间差计算,但太过于繁琐古板。

在这里插入图片描述
在这里插入图片描述

实现:我们采用动态代理对旧有的方法进行升级,动态代理是面向切面编程最主流的实现,而SpringAOPSpring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

优势:

在这里插入图片描述

02 Spring AOP快速入门:统计各个业务层方法执行耗时

① 导入依赖,在pom.xml中导入AOP的依赖

在这里插入图片描述

<!--AOP依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

② 编写AOP程序,针对特定方法根据业务需要进行编程

在这里插入图片描述

package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Aspect //AOP类
public class TimeAspect {
@Around("execution(* com.itheima.service.*.*(..))") //切入点表达式
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable{
//1.记录开始时间
long begin = System.currentTimeMillis(); //当前时间毫秒值
//2.调用原始方法
Object result = joinPoint.proceed();
//3.记录结束时间
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature() + "方法执行耗时:{}ms", end - begin);
return  result;
}
}

03 AOP核心概念

连接点:JoinPoint,是可以被AOP控制的众多方法

通知:Advice,可重复逻辑,也就是共性功能(最终体现为一个方法

切入点表达式:PointCut Expression,描述匹配条件

切入点:PointCut,符合匹配条件

切面:Aspect,通知 + 切入点表达式

目标对象:Target,字面意思

在这里插入图片描述

04 AOP执行流程

在这里插入图片描述

05 通知类型

在这里插入图片描述

MyAspect1.java

package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Component
//@Aspect
public class MyAspect1 {
//抽取切入点表达式哈哈哈哈哈
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){}
@Before("pt()") //前置通知
public void before(){
log.info("before ...");
}
@Around("pt()") //环绕通知
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after ...");
return result;
}
@After("pt()") //后置通知
public void after(){
log.info("after ...");
}
@AfterReturning("pt()") //返回后通知
public void afterReturning(){
log.info("afterReturning ...");
}
@AfterThrowing("pt()") //异常后通知
public void afterThrowing(){
log.info("afterThrowing ...");
}
}

在这里插入图片描述

@PointCut的作用是抽取公共的冗余的切入点表达式,引入pt()即可

在这里插入图片描述

06 通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行,执行顺序依照类名首字母,目标方法前的通知方法,字母排名靠前的先执行,目标方法后的通知方法,字母排名靠前的后执行,另外,注解@Order也可以决定执行顺序,目标方法前的通知方法,数字小的先执行,目标方法后的通知方法,数字小的后热行。
在这里插入图片描述

在这里插入图片描述

07 切入点表达式

切入点表达式用来决定项目中到底哪些目标方法需要应用我们所定义的通知,常见形式如下;

在这里插入图片描述

@execution()

@execution()主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符❓返回值 包名.类名. ❓方法名(方法参数)throws 异常❓)

其中带❓的表示可以省略的部分

  • 访问修饰符:可省略(比如:publicprotected)
  • 包名.类名:可省略
  • throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

在这里插入图片描述

通配符

*:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

execution(* com.*.service.*.update*())

..:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

execution(*com.itheima..Deptservice.*(..))

书写建议

  • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是find开头,更新类方法都是update开头
  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用,使用*匹配单个包

@annotation()

@annotation()用于匹配标识有特定注解的方法

@annotation(com.itheima.anno.Log)

在这里插入图片描述

MyLog.java自定义注解(标识)

package com.itheima.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLog {
}

08 连接点

Spring当中采用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,比如目标类名、方法名、方法参数等。注意,对于@Around通知,获取连接点信息只能用ProceedingJoinPoint,对于其他四种通知,取连接点信息只能用JoinPoint,其是ProceedingJoinPoint的父类型。

在这里插入图片描述

package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//切面类
@Slf4j
@Aspect
@Component
public class MyAspect8 {
@Pointcut("execution(* com.itheima.service.DeptService.*(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("MyAspect8 ... before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("MyAspect8 around before ...");
//1. 获取 目标对象的类名 
String className = joinPoint.getTarget().getClass().getName(); //目标→类→名字
log.info("目标对象的类名:{}", className);
//2. 获取 目标方法的方法名 
String methodName = joinPoint.getSignature().getName(); //方法签名→名字
log.info("目标方法的方法名: {}", methodName);
//3. 获取 目标方法运行时传入的参数 
Object[] args = joinPoint.getArgs(); //参数
log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));
//4. 放行 目标方法执行 
Object result = joinPoint.proceed();
//5. 获取 目标方法运行的返回值 
log.info("目标方法运行的返回值: {}", result);
log.info("MyAspect8 around after ...");
return result;
}
}

09 案例:将案例中增、删、改相关接口的操作日志记录到数据库表中

日志信息包含操作人、操作时间、实行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长等,此时,需要对所有业务类中的增、删、改方法添加统一功能,使用AOP技术最为划算。

在这里插入图片描述

准备工作:

① 引入AOP依赖包

<!--AOP--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

② 导入准备好的数据库表结构,引入对应实体类

-- 操作日志表
create table operate_log(
id int unsigned primary key auto_increment comment 'ID',
operate_user int unsigned comment '操作人ID',
operate_time datetime comment '操作时间',
class_name varchar(100) comment '操作的类名',
method_name varchar(100) comment '操作的方法名',
method_params varchar(1000) comment '方法参数',
return_value varchar(2000) comment '返回值',
cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

编码工作:

① 自定义注解@Log

package com.itheima.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Target(ElementType.METHOD) //当前注解作用在方法上
public @interface Log {
}

② 定义切面类、完成记录操作日志的逻辑,获取request对象,从请求头中获取jwt令牌,解析令牌当前用户的id

package com.itheima.aop;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
@Slf4j //输出日志
@Component
@Aspect //切面类
public class LogAspect {
@Autowired
private HttpServletRequest request;
//注入Mapper接口
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.itheima.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable{
//1.操作员工ID(借助jwt令牌)
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
//2.操作时间
LocalDateTime operateTime = LocalDateTime.now();
//3.操作类名
String className = joinPoint.getTarget().getClass().getName();
//4.操作方法名
String methodName = joinPoint.getSignature().getName();
//5.操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
//6.操作方法返回值
String returnValue = JSONObject.toJSONString(result);
//7.操作时间
Long costTime = end - begin;
//记录操作日志
OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);
operateLogMapper.insert(operateLog);
log.info("AOP记录操作日志,{}", operateLog);
return result;
}
}

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

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

相关文章

2025年7款与Jira数据同步的实用国产优秀项目管理软件对比

为什么我们需要这篇对比? 如果你是一位项目经理,或许经历过这些困扰:团队用惯了Jira,但因数据合规要求急需国产替代,却担心同步成本高、功能断层? 想找一款能与Jira无缝对接的工具,兼顾本地化服务与灵活扩展,却…

ESP8266 PMW使用的简单介绍

前言 呼吸灯是常见的LED应用。LED呼吸灯的流程是缓缓点亮LED灯,再缓缓熄灭LED灯。在夜色下,缓慢闪烁的LED显得格外迷人。 如何使用ESP8266MOD实现此效果, 这里简单地总结一下。 一、PWM单元介绍ESP8266有四个PWM输出…

DevEco Testing全面解析:HarmonyOS测试框架与实战指南 - 教程

DevEco Testing全面解析:HarmonyOS测试框架与实战指南 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Con…

C#知识学习-015(修饰符_4) - 详解

C#知识学习-015(修饰符_4) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco…

加州新规要求AI必须表明其AI身份

加州通过SB 243法案,要求伴侣聊天机器人必须明确告知用户其AI身份,并建立自杀预防报告机制。该法案旨在保护儿童安全,要求AI开发者实施防护措施,防止用户误以为在与真人交流。加州新规要求AI必须表明其AI身份 一项…

详细介绍:【rabbitmq 高级特性】全面详解RabbitMQ TTL (Time To Live)

详细介绍:【rabbitmq 高级特性】全面详解RabbitMQ TTL (Time To Live)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: &…

第三台中转机实现远程scp文件到远程

点击查看代码 命令 scp -3r user@ip:/path/file user@ip:/path/ 远程主机(源)-》中转机-》远程主机(目标)如果要实现免密需要 ssh-copy-id user@ip 将本地的 SSH 公钥快速复制到远程主机的 ~/.ssh/authorized_key…

单片机使用同一硬件定时器实现多周期定时功能

一个复杂的单片机程序可能需要很多种周期不同的定时器,用于执行不同的任务,如传感器数据采集、显示设备刷新或者执行设备的驱动等。如果每种周期使用一个单片机的硬件定时器将很难实现全部的功能需求,本文记录一种简…

(二十六)、Kuboard 部署网络问题 k8s 使用本地镜像 k8s使用 register本地镜像站 综合应用 - 实践

(二十六)、Kuboard 部署网络问题 &k8s 使用本地镜像 & k8s使用 register本地镜像站 综合应用 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displa…

低代码平台底层协议设计

低代码平台底层协议设计 1. 核心协议架构 1.1 协议分层设计 // 低代码平台协议栈 interface LowCodeProtocolStack {// 1. 传输层协议transport: TransportProtocol;// 2. 数据描述协议schema: SchemaProtocol;// 3. 组…

从PHP到Spring Boot:思维的转变与入门实战 (指南二) - 教程

从PHP到Spring Boot:思维的转变与入门实战 (指南二) - 教程2025-10-14 12:27 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !impor…

Vue 低代码平台渲染引擎设计

Vue 低代码平台渲染引擎设计 1. 核心架构设计 1.1 整体架构 // 渲染引擎核心接口定义 interface RenderEngine {schema: PageSchema; // 页面Schemacomponents: ComponentMap; // 组件映射dataSource: D…

微前端架构:实战指南与未来趋势 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

基于海思Hi3798MV200 Android7.0达成电影播放蓝光导航功能

基于海思Hi3798MV200 Android7.0达成电影播放蓝光导航功能pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consola…

2025 年热处理钎焊炉工装夹具厂家推荐榜:钎焊炉用耐热钢工装夹具厂家,聚焦品质与适配,助力企业高效生产

随着制造业对热处理工艺精度要求的不断提升、设备耐用性需求增强及生产标准化推进,热处理钎焊炉工装夹具已从高端冶金、核工业领域逐步拓展至石油、化工、电力、矿山等多个行业,2025 年市场规模预计持续增长。但市场…

实用指南:基于Spring Boot与SSM的社团管理系统架构设计

实用指南:基于Spring Boot与SSM的社团管理系统架构设计pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

请求超时重试封装

请求超时重试封装 1. 基础版本 - 带指数退避的重试机制 interface RetryConfig {maxRetries?: number; // 最大重试次数baseDelay?: number; // 基础延迟时间(ms)timeout?: number; …

完整教程:数据结构 01 线性表

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

编程脉络梳理

编程脉络梳理编程脉络梳理 Java基础 源码和原理ThreadLocal 内存溢出问题 和 java引用类型定时任务Timer的原理和使用hashMap扩容和转红黑树条件Serializable接口 和 serialVersionUID 的关系指针压缩原理和为什么指针…