一、什么是AOP切面?
在JAVA中,AOP(面向切面编程)中的切面(Aspect)是一种编程概念,用于横切关注点(cross-cutting concerns)。横切关注点是指那些与业务逻辑无关的,但是会经常重复使用到的功能,例如日志响应,权限校验。
主要作用:
- 模块化横切关注点:将横切关注点从业务逻辑代码中抽取出来,提高代码的可读性和可维护性
- 减少代码冗余:避免在多个地方重复编写相同的代码
- 提高代码复用性:切面可以在不同的运用程序上重复调用
- 可插拔性:可以通过配置或者运行时添加切面,无需修改现有代码
核心概念:
- 切面(Aspect):切面是一个模块化的横切关注点实现,它包括了连接点和通知。切面通常是一个类,里面可以定义切入点和通知。可以通过配置文件、注解等方式定义切面。比如我们这次编写的实现日志响应的这个class文件就是一个切面。
- 连接点(Joinpoint):程序中能够被切面插入的点,典型的连接点包括方法调用、方法执行中某个时点等等,在Spring AOP中,连接点通常指的是方法调用。比如我们这次实现日志响应,所涉及到查看日志的包下的所有方法的调用点
-
通知(Advice):在连接点处执行的代码。通知分为各种类型,包括前置通知(Before advice)、后置通知(After advice)、返回通知(After returning advice)、异常通知(After throwing advice)和环绕通知(Around advice)等。
-
切点(PointCut):用于定义哪些连接点上应该运用的通知。切点通过表达式进行定义,如匹配所有public方法或匹配某个包下的所有方案等。比如我们这次实现controller包下所有方法日志响应。
二、编写代码
@Aspect //注解:这是一个AOP切面
@Component //标注这是一个springboot容器管理的组件,会自动扫描注册为spring Bean,自动注入依赖
@Slf4j
public class LogInterceptor {
//执行拦截
@Around("execution(* com.springbootinit.controller.*.*(..))")
//@Around 注解表示在匹配的方法执行前后进行增强处理。
//这段代码定义了一个切点表达式,用于拦截 com.springbootinit.controller 包及其子包下的所有方法。
//切点:用于定义哪些连接点上应该运用的通知。
//execution(* ...) 表示匹配方法的签名,* 表示返回值类型任意,.. 表示参数列表任意。
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
//参数 joinPoint 表示被拦截的方法的连接点
//连接点(Joinpoint):程序中能够被切面插入的点,典型的连接点包括方法调用、方法执行中某个时点等等,
// 在Spring AOP中,连接点通常指的是方法调用
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//stopWatch的start()方法是一个计时器的开始计时的方法,用于记录当前时间作为计时器的起始时间。
//具体逻辑如下:
//检查当前是否有正在运行的任务(currentTaskName != null),如果有则抛出异常,表示不能重复启动。
//如果没有正在运行的任务,则记录当前任务名称和启动时间(使用纳秒级时间戳)
// 获取请求路径
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
//RequestContextHolder.currentRequestAttributes()是一个用于获取当前请求的RequestAttributes对象的工具方法。
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
//将 RequestAttributes转换为 ServletRequestAttributes 类型,并通过其 getRequest() 方法获取 HttpServletRequest 对象。
// 生成请求一个随机的、唯一 id(字符串),作为请求的唯一标识符
String requestId = UUID.randomUUID().toString();
String url = httpServletRequest.getRequestURI();
// 获取请求参数
Object[] args = point.getArgs();
//获取拦截方法连接点的所有参数(没有固定的类型,就用Object数组)
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
//使用 StringUtils.join 方法将数组元素用逗号拼接成字符串
// 输出请求日志
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
httpServletRequest.getRemoteHost(), reqParam);
// 执行原方法
Object result = point.proceed();//表示执行被拦截的目标方法
// 输出响应日志
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
//放行
return result;
}
}