应用场景
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
针对特定方法根据业务需要进行编程
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.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Aspect // 表明:当前类是AOP类
public class TimeAspect {
@Pointcut("execution(* com.ming.service.*.*(..))") // 切入点表达式的注解【切点】
private void pointCut(){}
/**
* 记录程序的运行时间
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 记录开始时间
long begin = System.currentTimeMillis();
// 2. 调用原始方法 result:原始方法的返回值
Object result = joinPoint.proceed();
// 3. 记录结束时间
long end = System.currentTimeMillis();
// 4. 打印运行时间
log.info(joinPoint.getSignature() + " ---> 方法耗时时间:" + (end - begin) + "ms");
return result;
}
}
连接点【joinPoint】:
可以被AOP控制的方法(暗含方法执行时的相关信息)
通知【Advice】:
重复的逻辑,共性功能(最终体现为一个方法)
切入点【PointCut】:
匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面【Aspet】:
描述通知与切入点的对应关系(通知 + 切入点)
目标对象【Target】:
通知所应用的对象
类型 | 说明 |
---|---|
@Around | 环绕通知,此注解标注的通知方法在目标方法前、后 都会被执行,一定要有返回值 |
@Before | 前置通知,此注解标注的通知方法在目标方法前 被执行 |
@After | 后置通知,此注解标注的通知方法在目标方法后 被执行,无论是否有异常都会执行 |
@AfterReturning | 返回后通知,此注解标注的通知方法在目标方法后 被执行,有异常不会执行 |
@AfterThrowing | 异常通知,此注解标注的通知方法发生异常 后执行 |
注意事项:
- @Around 环绕通知需要自己调用
ProceedingJoinPoint.proceed()
来让原始方法执行,其他通知不需要考虑目标方法执行。- @Around 环绕通知方法的返回值,必须指定为
Object
,来接收原始方法的返回值。
当有多个切面的切入点都会匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。
切入点表达式:描述切入点方法的一种表达式。主要用来决定项目中的哪些方法需要加入通知。
注意:如果要使用一个切入点表示两个不同的方法,可以使用 || 进行连接
/**
* 自定义标识注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
方法 | 说明 |
---|---|
joinPoint.getTarget().getClass().getName() | 获取目标类名 |
joinPoint.getSignature() | 获取目标方法签名 |
joinPoint.getSignature().getName() | 获取目标方法名 |
joinPoint.getArgs() | 获取目标方法运行参数 |
joinPoint.proceed() | 调用原始方法 获取返回值 Object (仅限于环绕通知ProceedingJoinPoint中使用 ) |
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80013
Source Host : localhost:3306
Source Schema : spingboot-mybatis-demo
Target Server Type : MySQL
Target Server Version : 80013
File Encoding : 65001
Date: 07/01/2024 23:02:57
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for operate_log
-- ----------------------------
DROP TABLE IF EXISTS `operate_log`;
CREATE TABLE `operate_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`operate_user` int(11) NULL DEFAULT NULL COMMENT '操作人ID',
`class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作的类名',
`method_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作的方法名',
`method_params` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方法参数',
`return_value` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方法返回值',
`operate_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间',
`cost_time` bigint(20) NULL DEFAULT NULL COMMENT '方法执行耗时ms',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 操作日志视图类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id;
private Integer operateUser;
private String className;
private String methodName;
private String methodParams;
private String returnValue;
private LocalDateTime operateTime;
private Long costTime;
}
import com.ming.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OperateLogMapper {
@Insert("insert into operate_log(operate_user, class_name, method_name, method_params, return_value, operate_time, cost_time) " +
"values (#{operateUser}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{operateTime}, #{costTime})")
public void insert(OperateLog operateLog);
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
import com.alibaba.fastjson.JSONObject;
import com.ming.mapper.OperateLogMapper;
import com.ming.pojo.OperateLog;
import com.ming.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
@Slf4j
@Component
@Aspect
public class OperateLogAspect {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
@Around("@annotation(com.ming.annotate.Log)")
public Object recordLg(ProceedingJoinPoint joinPoint) throws Throwable {
// 开始时间
long startTime = System.currentTimeMillis();
// 操作人 ---> 登陆人ID,根据请求头中JWT令牌获取登陆人ID
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
// 操作类名
String className = joinPoint.getTarget().getClass().getName();
// 操作方法名
String methodName = joinPoint.getSignature().getName();
// 方法参数
String methodParams = Arrays.toString(joinPoint.getArgs());
// 操作时间
LocalDateTime operateTime = LocalDateTime.now();
// 执行原始方法
Object proceed = joinPoint.proceed();
// 返回值:Object(对象) 转 字符串 ---> JSONObject.toJSONString
String returnValue = JSONObject.toJSONString(proceed);
// 结束时间
long endTime = System.currentTimeMillis();
// 耗时:ms
Long costTime = endTime - startTime;
// 记录日志
operateLogMapper.insert(new OperateLog(null, operateUser, className, methodName, methodParams, returnValue, operateTime, costTime));
return proceed;
}
}