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

SpringAOP、连接点、通知类型、通知顺序、切入点表达式

一. AOP入门

        1. AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),可简单的理解为面向特定方法编程

        2. 场景:部分业务方法运行较慢,定位执行耗时较长的接口,此时需要统计每一个业务的执行耗时,传统模式:在每一个业务方法中都需要计算耗时,AOP模式:可以统一管理计算(如记录系统的操作日志、事务管理、权限控制等等);

        3. 优势

                (1) 减少重复代码

                (2) 代码无侵入

                (3) 提高开发效率

                (4) 维护方便

        4.  AOP是一种思想,而在Spring框架中对这种思想进行的实现就是Spring AOP

二. 入门程序

        例如统计所有业务层方法的执行耗时

        1. 导入依赖:在pom.xml文件中引入AOP的依赖

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

        2. 编写AOP程序:针对于特定的方法根据业务需要进行编程


/*
* 入门程序 如统计所有业务层方法的执行耗时
* */
@Slf4j
@Aspect // 标识当前是一个AOP类/切面类
@Component
public class RecordTimeAspect {@Around("execution(* com.wyyzs.service.impl.*.*(..))") // com.wyyzs.service.impl 针对包下所有类所有方法生效public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// 1. 记录方法运行的开始时间Long startTime = System.currentTimeMillis();// 2. 执行原始方法Object result =  joinPoint.proceed();// 3. 记录方法运行的结束时间 记录耗时Long endTime = System.currentTimeMillis();log.info(joinPoint.getSignature() + "方法执行耗时"+(endTime-startTime));return result;}
}

        3. 执行业务层方法结果

三. AOP核心概念

        1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

        2. 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)

        3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用( 切入点一定是连接点,连接点不一定是切入点)

        4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

        5. 目标对象:Target,通知所应用的对象

        5. AOP执行流程

四. 通知类型

        1. 根据通知方法执行时机的不同,将通知类型分为以下常见的五类:

                (1) @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(需要自己调用ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行,环绕方法有返回值,必须指定为Object,来接受原始方法的返回值  )       

                (2) @Before:前置通知,此注解标注的通知方法在目标方法前被执行

                (3) @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

                (4) @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

                (5) @AfterThrowing:异常后通知,此注解标注的方法发生异常后执行


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class AspectTest1 {/** 前置通知,目标方法运行之前运行* */@Before("execution(* com.wyyzs.service.impl.*.*(..))")public void before(){log.info("before 前置通知执行了");}/** 环绕通知 - 目标方法执行前、后运行* */@Around("execution(* com.wyyzs.service.impl.*.*(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("around  环绕通知 - 目标方法执行前执行了");// 1. 记录方法运行的开始时间Long startTime = System.currentTimeMillis();// 2. 执行原始方法Object result =  joinPoint.proceed();// 3. 记录方法运行的结束时间 记录耗时Long endTime = System.currentTimeMillis();log.info(joinPoint.getSignature() + "方法执行耗时"+(endTime-startTime));log.info("around环绕通知 - 目标方法执行后执行了");return result;}/** 后置通知 无论是否有异常都会执行* */@After("execution(* com.wyyzs.service.impl.*.*(..))")public void after(){log.info("after 后置通知执行了");}/** 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行* */@AfterReturning("execution(* com.wyyzs.service.impl.*.*(..))")public void afterReturning(){log.info("AfterReturning返回后通知执行了");}/** 异常后通知,此注解标注的方法发生异常后执行* */@AfterThrowing("execution(* com.wyyzs.service.impl.*.*(..))")public void afterThrowing(){log.info("afterThrowing异常后通知执行了");}}

        2. @PointCut

                (1)该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可

                (2)private 修饰:仅能在当前切面类中引用该表达式;public修饰:在其他外部的切面类中也可以引用该表达式


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class AspectTest1 {@Pointcut("execution(* com.wyyzs.service.impl.*.*(..))")private void pc(){}/** 前置通知,目标方法运行之前运行* */@Before("pc()")public void before(){log.info("before 前置通知执行了");}/** 环绕通知 - 目标方法执行前、后运行* */@Around("pc()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("around  环绕通知 - 目标方法执行前执行了");// 1. 记录方法运行的开始时间Long startTime = System.currentTimeMillis();// 2. 执行原始方法Object result =  joinPoint.proceed();// 3. 记录方法运行的结束时间 记录耗时Long endTime = System.currentTimeMillis();log.info(joinPoint.getSignature() + "方法执行耗时"+(endTime-startTime));log.info("around环绕通知 - 目标方法执行后执行了");return result;}/** 后置通知 无论是否有异常都会执行* */@After("pc()")public void after(){log.info("after 后置通知执行了");}/** 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行* */@AfterReturning("pc()")public void afterReturning(){log.info("AfterReturning返回后通知执行了");}/** 异常后通知,此注解标注的方法发生异常后执行* */@AfterThrowing("pc()")public void afterThrowing(){log.info("afterThrowing异常后通知执行了");}}

五. 通知顺序

        1. 当有多个切面的切入点都匹配到了目标方法,在目标方法运行时多个通知方法都会被执行 

        2. 执行顺序:不同切面类中,默认按照切面类的类字母排序 

                (1) 目标方法前的通知方法:字母排名靠前的先执行

                (2) 目标方法后的通知方法:字母排名靠前的后执行


@Slf4j
@Aspect
@Component
public class AspectTest2 {@Pointcut("execution(* com.wyyzs.service.impl.*.*(..))")private void pc(){}/** 前置通知,目标方法运行之前运行* */@Before("pc()")public void before(){log.info("AspectTest2 前置通知执行了");}/** 后置通知 无论是否有异常都会执行* */@After("pc()")public void after(){log.info("AspectTest2 后置通知执行了");
}

        同理 AspectTest3  AspectTest4

        3. 用@Order(数字)加在切面类上来控制顺序

                (1) 目标方法前的通知方法:数字小的先执行

                (2) 目标方法后的通知方法:数字小的后执行

六. 切入点表达式

        介绍:描述切入点方法的一种表达式

        作用:用来决定项目中哪些方法需要加入通知

        常见形式:execution(.....) :根据方法的签名来匹配;@annotation(....):根据注解匹配

        1. execution

                (1) execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

                       ① ? 表示可以省略的部分

                       ②  访问修饰符:可省略(如 public、protected)

                       ③ 包名.类名:可省略(不建议省略,如果省略则全项目扫描方法)

                       ④ throws 异常:可省略(方法上声明抛出的异常,不是实际抛出的异常)

                (2) 可以使用通配符描述切入点

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

                        ② .. :多个连续的任意符号,可以通配任意层级的包或任意类型任意个数的参数(execution(* com.wyyzs..DeptService.*(..)))

                        ③:根据业务需要,可以使用 且(&&)、或(||)、非(!)来组合比较复杂的切入点表达式


@Slf4j
@Aspect
@Component
public class AspectTest5 {/** 前置通知,目标方法运行之前运行* */// @Before("execution(public void com.wyyzs.service.impl.DeptServiceImpl.delete(java.lang.Integer))") // 完整等表达式//@Before("execution(void com.wyyzs.service.impl.DeptServiceImpl.delete(java.lang.Integer))") // 省略访问修饰符//@Before("execution(void delete(java.lang.Integer))") // 省略包名.类名 不建议省略//@Before("execution(* com.wyyzs.service.impl.DeptServiceImpl.delete(java.lang.Integer))") //返回值任意//@Before("execution(* com.*.service.impl.DeptServiceImpl.delete(java.lang.Integer))") //com包下的任意一级包中的.service.impl.DeptServiceImpl.delete(java.lang.Integer))方法// @Before("execution(* com.wyyzs.service.impl.*.delete(java.lang.Integer))")// com.wyyzs.service.impl 包下所有类中的 delete(java.lang.Integer)方法// @Before("execution(* com.wyyzs.service.impl.*.*(java.lang.Integer))") //com.wyyzs.service.impl 包下所有类中的 形参是Integer 所有方法//@Before("execution(* com.wyyzs.service.impl.*.*(*))") //com.wyyzs.service.impl 包下所有类中的 单个任意类型形参的 所有方法// @Before("execution(* com.wyyzs.service.impl.*.del*(*))") // com.wyyzs.service.impl 包下所有类中的 以 del开头的 单个任意类型形参的 所有方法//@Before("execution(* com.wyyzs.service.impl.*.*e(*))") // com.wyyzs.service.impl 包下所有类中的 以 e结尾 单个任意类型形参的 所有方法// @Before("execution(* com..service.impl.DeptServiceImpl.delete(java.lang.Integer))") //com包下的任意层包// @Before("execution(* com..service.impl.*.*(..))") // 任意类型 任意个数 形参方法// @Before("execution(* com.wyyzs.service.*.*(..))") // com.wyyzs.service下所有接口的所有方法// 匹配 findAll 与delete方法 可以通过逻辑运算符 || && !@Before("execution(* com.wyyzs.service.impl.DeptServiceImpl.findAll(..)) || execution(* com.wyyzs.service.impl.DeptServiceImpl.delete(..))")public void before(){log.info("AspectTest4 前置通知执行了");}}

                (3) 书写建议

                        ① 所有业务方法在命名时尽量规范,方便切入点表达式快速匹配(如findXxx、deleteXxx)

                        ② 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性

                        ③ 在满足业务需求的前提下,尽量缩小切入点的匹配范围,如包名尽量不使用..,使用*匹配单个包

        2. @annotation

                (1) @annotation 切入点表达式,用于匹配标识有特定注解的方法

        

/*
* 定义注解
* */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTest {
}
@Slf4j
@Aspect
@Component
public class AspectTest6 {@Before("@annotation(com.wyyzs.anno.LogTest)") // 例如 使用LogTest注解的方法public void before(){log.info("AspectTest4 前置通知执行了");}}

七.  连接点JoinPoint

        1. 在Spring中使用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等

                (1) 对于@Around通知,获取连接点信息只能使用 ProceedingJoinPoint

                (2) 对于其他四种通知,获取连接点信息只能使用JoinPoint,他是ProceedingJoinPoint的父类型


@Slf4j
@Aspect
@Component
public class AspectTest7 {@Before("execution(* com.wyyzs.service.impl.DeptServiceImpl.*(..))")public void before(JoinPoint joinPoint){log.info("before 前置通知执行了");//1. 获取目标对象Object target=joinPoint.getTarget();log.info("获取目标对象{}" , target);// 2. 获取目标类String className = target.getClass().getName();log.info("获取目标类{}" , className);// 3.获取目标方法String methodName = joinPoint.getSignature().getName();log.info("获取目标方法{}" , methodName);// 4. 获取目标方法参数Object[] args = joinPoint.getArgs();log.info("获取目标方法参数{}" , Arrays.toString(args));}// @Around("execution(* com.wyyzs.service.impl.DeptServiceImpl.*(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable{log.info("around before 执行了");//1. 获取目标对象Object target=joinPoint.getTarget();log.info("around获取目标对象{}" , target);// 2. 获取目标类String className = target.getClass().getName();log.info("around获取目标类{}" , className);// 获取目标方法签名Signature  signature = joinPoint.getSignature();log.info("around获取目标方法签名{}" , signature);// 3.获取目标方法String methodName = joinPoint.getSignature().getName();log.info("around获取目标方法{}" , methodName);// 4. 获取目标方法参数Object[] args = joinPoint.getArgs();log.info("around获取目标方法参数{}" , Arrays.toString(args));return joinPoint.proceed();}
}

http://www.dtcms.com/a/611541.html

相关文章:

  • 平面设计师参考网站开发公司总经理竞聘报告
  • 手机可以看的网站如何查看网站是什么语言做的
  • 电源完整性11-电容安装电感的影响因素
  • 手机如何做车载mp3下载网站互联网公司排名深信服
  • 电商网站建设定制北京网站定制价格表
  • 水利网站建设管理汇报广告网站制作多少钱
  • 新建茶叶网站文章内容建设电子商务网站建设工资
  • SAP 如何恢复电子表格EXCEL导出允许选择格式
  • 瓦房店 网站建设二级域名怎么解析
  • 国外做化学申报的网站文化类网站是不是休闲娱乐类网站
  • 最新版LangChain 1.0快速入门介绍
  • 广州开发网站服务设计一个电商网站的首页
  • 单页网站开发网站开发初学
  • 怎样建公司网站有什么网站可以做宣传图片
  • 易缴缴:开启注册资金实缴无忧新时代
  • Java基础——数组1
  • 关于做公司网站成都企业管理培训课程
  • 新开传奇手游发布网站网站首页设计多少钱
  • 设计企业门户网站org是国外的网站吗
  • ACL 2025论文分享|一种同时支持文字、语音、草图、艺术图和低分辨率图等多模态内容检索的新框架Uni-Retrieval
  • 免费在线网站xp系统建设网站
  • 专利协会网站建设方案cpu占用超出网站空间的分配值
  • Kubernetes 证书以及证书续期、过期处理
  • 单页面竞价网站厦门湖里区建设局网站
  • YOLOv5 详细讲解文档
  • 计算机网络技术网站开发优化好的网站
  • 网站备案账号是什么石家庄微网站建设
  • 营销网站和展示型网站维护一个网站
  • 西安网站建设制作专业公司创建公司为什么必须三个人
  • 广安做网站公司海口网络公司网站建设