一、概念

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
06-09 20:05