一、概念
AOP,也就是 Aspect-oriented Programming,译为面向切面编程,是计算机科学中的一个设计思想,旨在通过切面技术为业务主体增加额外的通知(Advice),从而对声明为“切点”(Pointcut)的代码块进行统一管理和装饰。
二、AOP 的相关术语
1.横切关注点,从每个方法中抽取出来的同一类非核心业务
2.切面(Aspect),对横切关注点进行封装的类,每个关注点体现为一个通知方法;通常使用 @Aspect 注解来定义切面。
3.通知(Advice),切面必须要完成的各个具体工作,比如我们的日志切面需要记录接口调用前后的时长,就需要在调用接口前后记录时间,再取差值。通知的方式有五种:
- @Before:通知方法会在目标方法调用之前执行
- @After:通知方法会在目标方法调用后执行
- @AfterReturning:通知方法会在目标方法返回后执行
- @AfterThrowing:通知方法会在目标方法抛出异常后执行
- @Around:把整个目标方法包裹起来,在被调用前和调用之后分别执行通知方法
4.连接点(JoinPoint),通知应用的时机,比如接口方法被调用时就是日志切面的连接点。
5.切点(Pointcut),通知功能被应用的范围,比如本篇日志切面的应用范围是所有 controller 的接口。通常使用 @Pointcut 注解来定义切点表达式。
切入点表达式的语法格式规范如下所示:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?)
modifiers-pattern?
为访问权限修饰符ret-type-pattern
为返回类型,通常用*
来表示任意返回类型declaring-type-pattern?
为包名name-pattern
为方法名,可以使用*
来表示所有,或者set*
来表示所有以 set 开头的类名param-pattern)
为参数类型,多个参数可以用,
隔开,各个参与也可以使用*
来表示所有类型的参数,还可以使用(..)
表示零个或者任意参数throws-pattern?
为异常类型?
表示前面的为可选项
举个例子:
@Pointcut("execution(public * com.example.quartzdemo.controller.*.*(..))")
表示 com.example.quartzdemo.controller
包下的所有 public 方法都要应用切面的通知。
三、操作
1.添加依赖
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建日志处理切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @author qinxun
* @date 2023-06-09
* @Descripion: 统一日志处理切面
*/
@Aspect
@Component
public class LogAspect {
/**
* 切点
*/
@Pointcut("execution(public * com.example.quartzdemo.controller.*.*(..))")
public void log() {
}
/**
* 通知 具体的操作
*/
@Around("log()")
public void doAround(ProceedingJoinPoint joinPoint) {
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取请求地址
String urlStr = request.getRequestURL().toString();
System.out.println(urlStr);
// 获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("方法名:" + methodName);
// 获取方法的参数
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println("方法的参数:" + arg);
}
// 获取到参数 我们可以持久化到数据库等。。。
}
}
4.创建测试类
示例代码如下:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author qinxun
* @date 2023-06-09
* @Descripion: aop测试
*/
@RestController
public class AspectController {
@RequestMapping("/aspect")
public String toAspect(String name) {
return "aspect:" + name;
}
}
5.启动测试
我们访问接口http://localhost:8080/aspect?name=qq
我们可以看到控制台打印出了我们访问接口的信息
2023-06-09 17:06:21.157 INFO 4192 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-06-09 17:06:21.165 INFO 4192 --- [ main] c.e.quartzdemo.QuartzDemoApplication : Started QuartzDemoApplication in 3.983 seconds (JVM running for 4.695)
2023-06-09 17:09:30.840 INFO 4192 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-06-09 17:09:30.841 INFO 4192 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-06-09 17:09:30.841 INFO 4192 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
http://localhost:8080/aspect
方法名:toAspect
方法的参数:qq