概述
AOP:Aspect Oriented Programming面向切面编程、面向方面编程。其实就是面向特定方法编程。
最主流的实现是动态代理
Spring AOP快速入门:统计各个业务层方法执行耗时
AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写AOP程序
package com.example.springbootmybatis.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Aspect//这个注释表示这个java文件为aop文件
public class TimeAspect {
@Around("execution(* com.example.springbootmybatis.service.*.*(..))")//作用在哪些方法上
// 根据实际需求修改切入点表达式,第一个星表示类名或者接口名,第二个星代表所有的方法名,可以把service包换成其他包名
// (..)表示任意参数
public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed(); // 调用原始方法
long end = System.currentTimeMillis();
log.info(proceedingJoinPoint.getSignature() + " 执行耗时: {}ms", end - begin);//方法名+参数+耗时
return result;
}
}
AOP应用场景
记录操作日志,极限控制,事务管理
优点
代码无侵入、减少重复代码、提高开发效率、维护方便
AOP核心概念
连接点:JoinPoint
通知:Advice
切入点:PointCut
切面:Aspect
目标对象:Target
AOP进阶
1.通知类型
@Around
package com.example.springbootmybatis.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Slf4j
@Component
@Aspect
public class Aspect1 {
// 环绕通知
@Around("execution(* com.example.springbootmybatis..*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("环绕通知:方法开始");
Object result = joinPoint.proceed();
log.info("环绕通知:方法结束");
return result;
}
// 前置通知
@Before("execution(* com.example.springbootmybatis..*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
log.info("前置通知:方法调用前");
}
// 后置通知
@After("execution(* com.example.springbootmybatis..*(..))")
public void afterAdvice(JoinPoint joinPoint) {
log.info("后置通知:方法调用后");
}
// 返回后通知
@AfterReturning(pointcut = "execution(* com.example.springbootmybatis..*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
log.info("返回后通知:方法返回 " + result);
}
// 异常后通知
@AfterThrowing(pointcut = "execution(* com.example.springbootmybatis..*(..))", throwing = "error")
public void afterThrowingAdvice(JoinPoint joinPoint, Throwable error) {
log.error("异常后通知:异常 " + error);
}
}
pointcut:解决代码重复。
如果想在其他切面类中引用,改为public即可
@Pointcut("execution(* com.example.springbootmybatis..*(..))")
public void pointcut() {}
// 环绕通知
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("环绕通知:方法开始");
Object result = joinPoint.proceed();
log.info("环绕通知:方法结束");
return result;
}
2.通知顺序
如果多个类下的通知完全一样,那么顺序是按照类目字母排序。
@Order(1)
3.切入点表达式
1.execution
包名和类名不建议省略,因为会导致范围过大,可能会运行多次。
//@Pointcut("execution(* com.example.springbootmybatis..*(..))")// .. 的意思是可以匹配 com.example.springbootmybatis 包及其所有子包下的所有类和方法。
@Pointcut("execution(* com.example.springbootmybatis.*service.delete*(..))")//匹配以service结尾,delete开头的方法
使用或连接两个方法
@Pointcut("execution(* com.example.springbootmybatis.service.DeptService.list()) || " +
"execution(* com.example.springbootmybatis.service.DeptService.delete(java.lang.Integer))")
2.annotation
自定义注解
package com.example.springbootmybatis.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//注解的位置
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)//什么时候执行
public @interface MyLog {
}
@Pointcut("@annotation(com.example.springbootmybatis.aop.MyLog)")
4.连接点
package com.example.springbootmybatis.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class Aspect2 {
@Pointcut("execution(* com.example.springbootmybatis..*(..))")
private void pt() {}
@Before("pt()")
public void before() {
log.info("Aspect2 ... before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Aspect2 around before ...");
// 1. 获取目标对象的类名
String className = joinPoint.getTarget().getClass().getName();
// 2. 获取目标方法的方法名
String methodName = joinPoint.getSignature().getName();
// 3. 获取目标方法运行时传入的参数
Object[] args = joinPoint.getArgs();
// 打印类名、方法名和参数
log.info("Executing " + className + "." + methodName + " with arguments: " + java.util.Arrays.toString(args));
// 4. 放行,执行目标方法
Object result = joinPoint.proceed();
// 5. 获取目标方法的返回值
log.info("MyAspect8 around after ... Result: " + result);
return result;
}
}
AOP案例
新建表
create table operate_log(
id int unsigned primary key auto_increment comment 'ID',
operate_user int unsigned comment '操作人ID',
operate_time datetime comment '操作时间',
class_name varchar(100) comment '操作的类名',
method_name varchar(100) comment '操作的方法名',
method_params varchar(1000) comment '方法参数',
return_value varchar(2000) comment '返回值',
cost_time bigint comment '方法执行耗时,单位:ms'
) comment '操作日志表';
实体类
package com.example.springbootmybatis.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; // ID
private Integer operateUser; // 操作人ID
private LocalDateTime operateTime; // 操作时间
private String className; // 操作类名
private String methodName; // 操作的方法名
private String methodParams; // 操作方法参数
private String returnValue; // 操作方法返回值
private Long costTime; // 操作耗时
}
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
实现类
package com.example.springbootmybatis.aop;
import com.alibaba.fastjson.JSONObject;
import com.example.springbootmybatis.mapper.OperateLogMapper;
import com.example.springbootmybatis.pojo.OperateLog;
import com.example.springbootmybatis.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
@Aspect
@Component
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.example.springbootmybatis.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取用户ID
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJwt(jwt);
Integer operateUser = (Integer) claims.get("id");
// 操作时间
LocalDateTime operateTime = LocalDateTime.now();
// 操作类名
String className = joinPoint.getTarget().getClass().getName();
// 操作方法名
String methodName = joinPoint.getSignature().getName();
// 操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
// 记录开始时间
long begin = System.currentTimeMillis();
// 调用目标方法运行
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
// 方法返回值
String returnValue = JSONObject.toJSONString(result);
// 计算耗时
Long costTime = end - begin;
// 记录操作日志
// 创建 OperateLog 对象
OperateLog operateLog = new OperateLog(
null,
operateUser,
operateTime,
className,
methodName,
methodParams,
returnValue,
costTime
);
// 插入日志到数据库
log.info("AOP操作日志:{}", operateLog);
operateLogMapper.insert(operateLog);
return result;
}
}
在增删改的控制层加上注解Log