一、AOP是什么?

Aspect Oriented Programming(AOP)面向切面编程,借用百度百科中的一句话"可以通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术",借此实现日志系统的搭建。

二、使用步骤

1.Maven依赖

代码如下(示例):

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

2.日志工具类

这里的方法都是自定义的,写好想要的逻辑之后,只需要加上对应的注解@Before,@After,@Around,他们都有一个String参数,是配置拦截切面的方法既添加@Pointcut注解的方法名。
首先需要在类上添加@Aspect注解功能才生效,AOP的启动注解为@EnableAspectJAutoProxy需要在启动类上添加,这里使用的springboot版本2.4.0,默认开启AOP功能所以启动类中没有添加@EnableAspectJAutoProxy注解

@Pointcut
参数说明:

  • execution():用于匹配方法执行的连接点
  • args(): 用于匹配当前执行的方法传入的参数为指定类型的执行方法
  • this(): 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
  • target(): 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
  • within(): 用于匹配指定类型内的方法执行;
  • @args(): 于匹配当前执行的方法传入的参数持有指定注解的执行;
  • @target(): 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
  • @within(): 用于匹配所以持有指定注解类型内的方法;
  • @annotation: 用于匹配当前执行方法持有指定注解的方法;

execution语法格式:
execution(
modifier-pattern?
ret-type-pattern
declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?
)

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

其中带 ?号的 modifiers-pattern?,declaring-type-pattern?,hrows-pattern?是可选项
ret-type-pattern,name-pattern, parameters-pattern是必选项;

modifier-pattern? 修饰符匹配,如public 表示匹配公有方法
ret-type-pattern 返回值匹配,* 表示任何返回值,全路径的类名等
declaring-type-pattern? 类路径匹配
name-pattern 方法名匹配,* 代表所有,set*,代表以set开头的所有方法
(param-pattern) 参数匹配,指定方法参数(声明的类型),

(…)代表所有参数,
()代表一个参数,
(,String)代表第一个参数为任何值,第二个为String类型.

throws-pattern? 异常类型匹配

例子:
execution(public * (…)) 定义任意公共方法的执行
execution(
set*(…)) 定义任何一个以"set"开始的方法的执行
execution(* com.xyz.service.AccountService.(…)) 定义AccountService 接口的任意方法的执行
execution(
com.xyz.service..(…)) 定义在service包里的任意方法的执行
execution(* com.xyz.service ….(…)) 定义在service包和所有子包里的任意类的任意方法的执行
execution(* com.test.spring.aop.pointcutexp…JoinPointObjP2.*(…)) 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行。

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Map;

@Slf4j
@Aspect
@Component
public class logConfig {


    @Pointcut(value = "execution(* wk.frame.controller.*.*(..))")
    public void webLog() {
        System.out.println("这里是webLog");
    }

    @Before("webLog()")
    public void beforeLog(JoinPoint joinPoint){
        System.out.println("@Before");
        //这里可以拿到请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取到请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        System.out.println("请求参数:"+ JSON.toJSONString(parameterMap));
        //获取请求uri
        String uri = request.getRequestURI();
        System.out.println("请求地址:"+uri);
        //获取请求头
        Enumeration<String> headerNames = request.getHeaderNames();
        //比较长不打印了
//        System.out.println("请求头:");
//        while(headerNames.hasMoreElements()){
//            String s = headerNames.nextElement();
//            System.out.println(s+":"+request.getHeader(s));
//        }
    }


    @After("webLog()")
    public void afterLog(){
         System.out.println("@After");
    }

    @Around("webLog()")
    public Object aroundLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //拦截controller这里return的内容可以直接返回到页面可以用来封装全局统一的数据格式(code,data,mes...)
        System.out.println("@Around - start");
        //让方法执行!如果没有该方法则请求被拦截
        Object res = proceedingJoinPoint.proceed();
        System.out.println("@Around - end");
        return "@Around方法中的return 原接口数据:"+res;
    }
}

这里贴上测试用的Controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StudentController {

    @GetMapping("test")
    public String testGet (String testArg){
     	System.out.println("controller测试方法执行!");
        return "测试数据";
    }
}

输出内容

@Around - start
@Before
请求参数:{"testArg":["1"]}
请求地址:/test
controller测试方法执行!
@After
@Around - end

总结

执行顺序:
有proceedingJoinPoint.proceed()时
@Around->@Before->Controller->@After->@Around
无proceedingJoinPoint.proceed()时
@Around
配置好以后就可以加入自己的逻辑打印请求日志,请求时间等信息了!