1. AOP简介
要介绍面向切面编程(Aspect-Oriented Programming,AOP),需要读者
首先考虑这样一个场景:
公司有一个人力资源管理系统目前已经上线,但是系统运行不稳定,有时运行得很慢,为了检测出到底是哪个环节出问题了,开发人员想要监控每一个方法的执行时间,再根据这些执行时间判断出问题所在。当问题解决后,再把这些监控移除掉。
系统目前已经运行,如果手动修改系统中成千上万个方法,那么工作量未免太大,而且这些监控方法以后还要移除掉;如果能够在系统运行过程中动态添加代码,就能很好地解决这个需求。
这种在系统运行时动态添加代码的方式称为面向切面编程(AOP)。
Spring框架对AOP提供了很好的支持。在AOP中,有一些常见的概念需要读者了解。
- Joinpoint(连接点): 类里面可以被增强的方法即为连接点。例如,想修改哪个方法的功能,那么该方法就是一个连接点。
- Pointcut(切入点): 对Joinpoint进行拦截的定义即为切入点。例如,拦截所有以insert开始的方法,这个定义即为切入点。
- Advice(通知): 拦截到Joinpoint之后所要做的事情就是通知。例如,上文说到的打印日志监控。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知。
- Aspect(切面):Pointcut和Advice的结合。
- Target(目标对象):要增强的类称为Target。
2. SpringBoot支持
SpringBoot在Spring的基础上对AOP的配置提供了自动化配置解决方案spring-boot-starter-aop,使开发者能够更加便捷地在SpringBoot项目中使用AOP。
配置步骤如下。
2.1 AOP依赖
首先在SpringBootWeb项目中引入spring-boot-starter-aop依赖,代码如下:
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 定义切面
接下来创建切面,代码如下:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAspect1 {
/**
* 切入点为:com.ruoyi.system.service包下的所有类的所有方法
*/
@Pointcut("execution(* com.ruoyi.system.service.*.* (..))")
public void pc1() {
}
/**
* 前置通知,对切入点pc1()进行增强
*
* @param jp 连接点:包含增强的方法信息
*/
@Before(value = "pc1()")
public void before(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println("Before 前置通知 >>" + name + "方法开始执行...");
}
/**
* 后置通知,对切入点pc1()进行增强
*
* @param jp 连接点:包含增强的方法信息
*/
@After(value = "pc1()")
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println("After 后置通知 >>" + name + "方法结束执行...");
}
/**
* 返回通知,对切入点pc1()进行增强
*
* @param jp 连接点:包含增强的方法信息
* @param result 方法返回值
*/
@AfterReturning(value = "pc1()", returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
String name = jp.getSignature().getName();
System.out.println("AfterReturning 返回通知 >>" + name + "方法返回值:" + result);
}
/**
* 异常通知,对切入点pc1()进行增强
*
* @param jp 连接点:包含增强的方法信息
* @param e 抛出的异常信息
*/
@AfterThrowing(value = "pc1()", throwing = "e")
public void afterReturning(JoinPoint jp, Exception e) {
String name = jp.getSignature().getName();
System.out.println("AfterThrowing 异常通知 >>" + name + "方法抛出异常,异常信息" + e.getMessage());
}
/**
* 环绕通知
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("pc1()")
public Object around(ProceedingJoinPoint pjp) {
String name = pjp.getSignature().getName();
System.out.println("Around 环绕通知 >>" + name + "方法开始执行...");
try {
return pjp.proceed();
} catch (Throwable e) {
System.out.println("Around 环绕通知 >>" + name + "方法发生异常...");
throw new RuntimeException(e);
} finally {
System.out.println("Around 环绕通知 >>" + name + "方法结束执行...");
}
}
}
代码解释:
- @Aspect注解表明这是一个切面类。
- 第12~14行定义的pc1方法使用了@Pointcut注解,这是一个切入点定义。execution中的第一个
*
表示方法返回任意值,第二个*
表示service包下的任意类,第三个*
表示类中的任意方法,括号中的两个点表示方法参数任意,即这里描述的切入点为service包下所有类中的所有方法。 - 第21~25行定义的方法使用了@Before注解,表示这是一个前置通知,该方法在目标方法执行之前执行。通过JoinPoint参数可以获取目标方法的方法名、修饰符等信息。
- 第32~36行定义的方法使用了@After注解,表示这是一个后置通知,该方法在目标方法执行之后执行。
- 第44~48行定义的方法使用了@AfterReturning注解,表示这是一个返回通知,在该方法中可以获取目标方法的返回值。@AfterReturning注解的returning参数是指返回值的变量名,对应方法的参数。注意,在方法参数中定义了result的类型为Object,表示目标方法的返回值可以是任意类型,若result参数的类型为Long,则该方法只能处理目标方法返回值为Long的情况。
- 第56~60行定义的方法使用了@AfterThrowing注解,表示这是一个异常通知,即当目标方法发生异常时,该方法会被调用,异常类型为Exception表示所有的异常都会进入该方法中执行,若异常类型为ArithmeticException,则表示只有目标方法抛出的ArithmeticException异常才会进入该方法中处理。
- 第69~81行定义的方法使用了@Around注解,表示这是一个环绕通知。环绕通知是所有通知里功能最为强大的通知,可以实现前置通知、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint对象的proceed方法使目标方法继续执行,开发者可以在此修改目标方法的执行参数、返回值等,并且可以在此处理目标方法的异常。
配置完成
测试文章来源:https://www.toymoban.com/news/detail-855805.html
17:17:35.368 [http-nio-8080-exec-1] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring DispatcherServlet 'dispatcherServlet'
Around 环绕通知 >>selectUserList方法开始执行...
Before 前置通知 >>selectUserList方法开始执行...
17:17:35.623 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList_COUNT - [debug,135] - ==> Preparing: SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0'
17:17:35.628 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList_COUNT - [debug,135] - ==> Parameters:
17:17:35.630 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList_COUNT - [debug,135] - <== Total: 1
17:17:35.633 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList - [debug,135] - ==> Preparing: select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u left join sys_dept d on u.dept_id = d.dept_id where u.del_flag = '0' LIMIT ?
17:17:35.634 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList - [debug,135] - ==> Parameters: 10(Integer)
17:17:35.637 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList - [debug,135] - <== Total: 2
AfterReturning 返回通知 >>selectUserList方法返回值:Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=10, total=2, pages=1, reasonable=true, pageSizeZero=false}
After 后置通知 >>selectUserList方法结束执行...
Around 环绕通知 >>selectUserList方法结束执行...
异常测试文章来源地址https://www.toymoban.com/news/detail-855805.html
Around 环绕通知 >>selectErr方法开始执行...
Before 前置通知 >>selectErr方法开始执行...
AfterThrowing 异常通知 >>selectErr方法抛出异常,异常信息null
After 后置通知 >>selectErr方法结束执行...
Around 环绕通知 >>selectErr方法发生异常...
Around 环绕通知 >>selectErr方法结束执行...
17:23:08.159 [http-nio-8080-exec-3] ERROR c.r.f.w.e.GlobalExceptionHandler - [handleRuntimeException,93] - 请求地址'/system/user/err',发生未知异常.
java.lang.RuntimeException: com.ruoyi.common.exception.ServiceException
**********************************************************
到了这里,关于4.12 SpringBoot整合AOP ❤❤❤的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!