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

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():根据方法的签名来匹配
  • @annotation():根据注解匹配

在这里插入图片描述

@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 {@Autowiredprivate HttpServletRequest request;//注入Mapper接口@Autowiredprivate 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.dtcms.com/a/420665.html

相关文章:

  • MYSQL —— 约束和多表查询
  • LAYER_INITCALL宏
  • M| 怪形
  • 在Linux中快速下载Conda的方法
  • 如何做自己的简历网站wordpress文库主题
  • 数据库-锁
  • 解决 Python Crypto安装后依然无效的问题
  • 数字化转型:概念性名词浅谈(第五十六讲)
  • solidworks 实体分割 保存实体 后转换钣金 钣门框出图 结构构件添加 钣金出图教程 平板样式不是展开解决办法 比例激活1比1
  • (Kotlin协程六)协程和RxJava的区别
  • 企业网站制作及cms技术网站开发文档教学
  • 比较好的建立站点如何修改wordpress主题
  • 想让客户公司做网站的话语wordpress s5主题
  • 网站制作框架公司网页怎么制作教程
  • 【实战】理解服务器流量监控中的“上行”和“下行”
  • 洛阳直播网站建设个人空间网站
  • MyBatis-Plus使⽤
  • 长春网站制作报价南京软件定制
  • 烟台网站建设方案wordpress推荐好友
  • 著名网站用什么语言做后台定制软件开发报价
  • 质量好网站建设商家建设网站的建设费用包括哪些内容
  • 《Linux 基础 IO 完全指南:从文件描述符到缓冲区》
  • 如何上传ftp网站程序c 做网站开发
  • 【Linux】库的制作与原理(1)
  • 网站建设策划书悠悠如何做百度竞价推广
  • NVIDIA Warp v1.9.0深度解析:GPU加速物理仿真与计算的革命性进展
  • 网站怎么挂广告有没有做字的网站
  • dede电影网站模版个人博客模板wordpress
  • 临清设计网站网站建立价格
  • 公司电子商务网站建设规划方案米拓网站建设步骤