当前位置: 首页 > news >正文

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

实用指南: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.hskmm.com/?act=detail&tid=30849

相关文章:

  • 微信机器人接口开发
  • 2025年7款与Jira数据同步的实用国产优秀项目管理软件对比
  • ESP8266 PMW使用的简单介绍
  • DevEco Testing全面解析:HarmonyOS测试框架与实战指南 - 教程
  • 本周第一单 多晶硅
  • 加州新规要求AI必须表明其AI身份
  • 详细介绍:【rabbitmq 高级特性】全面详解RabbitMQ TTL (Time To Live)
  • 第三台中转机实现远程scp文件到远程
  • 单片机使用同一硬件定时器实现多周期定时功能
  • 低代码平台底层协议设计
  • 基于海思Hi3798MV200 Android7.0达成电影播放蓝光导航功能
  • Vue 低代码平台渲染引擎设计
  • 2025 年热处理钎焊炉工装夹具厂家推荐榜:钎焊炉用耐热钢工装夹具厂家,聚焦品质与适配,助力企业高效生产
  • 实用指南:基于Spring Boot与SSM的社团管理系统架构设计
  • 请求超时重试封装
  • Emacs常用的一些快捷键,记不住的,方便查询!!
  • Microsoft Visual C++,Microsoft Visual Studio for Office Runtime,Microsoft Visual Basic Runtime等下载
  • 2025 年耐热钢厂家及热处理工装设备厂家推荐榜:多用炉/真空炉/台车炉/井式炉/箱式炉/耐热钢工装厂家,聚焦高效适配,助力企业精准选型
  • 实用指南:如何进行WGBS的数据挖掘——从甲基化水平到功能通路
  • python对接印度尼西亚股票数据接口文档
  • Webpack优化
  • 2025年舒适轮胎厂家最新权威推荐榜:静音耐磨,驾驶体验全面升级!
  • 2025年耐磨轮胎厂家最新推荐排行榜,矿山耐磨轮胎,工程耐磨轮胎,重载耐磨轮胎公司推荐!
  • Map做数据缓存
  • Python基于 Gradio 和 SQLite 开发的简单博客管理平台,承受局域网手机查看,给一个PC和手机 互联方式
  • RK3576+gc05a2
  • 2025 年工业表面处理领域喷砂机厂家最新推荐排行榜,涵盖智能自动化可移动等类型设备优质厂家
  • 2025.10.14
  • 行列式按多行或列展开
  • 2025 年化妆品代工厂最新推荐排行榜:OEM/ODM/ 私人定制等服务优选企业指南