JavaWeb-Springboot-AOP

目录

  • 一、概述
  • 二、导入POM依赖
  • 三、编写AOP程序
  • 四、核心概念
  • 五、通知类型
  • 六、通知顺序
  • 七、切入点表达式
    • 7.1 execution(...):根据方法签名匹配【通配】
    • 7.2 @annotation(注解全类名):根据注解匹配【自定义注解】
  • 八、连接点
  • 八、案例:记录操作日志
    • 8.1 创建数据库表,封装对应的实体类,并编写插入语句
    • 8.2 自定义注解
    • 8.3 标注哪些操作需要增加日志
    • 8.4 定义AOP切面


一、概述

应用场景

在这里插入图片描述优势

JavaWeb-Springboot-AOP_第1张图片

二、导入POM依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

三、编写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.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】:通知所应用的对象

JavaWeb-Springboot-AOP_第2张图片
执行流程

JavaWeb-Springboot-AOP_第3张图片

五、通知类型

类型 说明
@Around 环绕通知,此注解标注的通知方法在目标方法前、后都会被执行,一定要有返回值
@Before 前置通知,此注解标注的通知方法在目标方法被执行
@After 后置通知,此注解标注的通知方法在目标方法被执行,无论是否有异常都会执行
@AfterReturning 返回后通知,此注解标注的通知方法在目标方法被执行,有异常不会执行
@AfterThrowing 异常通知,此注解标注的通知方法发生异常后执行

注意事项:

  • @Around 环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行。
  • @Around 环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

JavaWeb-Springboot-AOP_第4张图片

六、通知顺序

当有多个切面的切入点都会匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

JavaWeb-Springboot-AOP_第5张图片

JavaWeb-Springboot-AOP_第6张图片

七、切入点表达式

切入点表达式:描述切入点方法的一种表达式。主要用来决定项目中的哪些方法需要加入通知。

JavaWeb-Springboot-AOP_第7张图片

7.1 execution(…):根据方法签名匹配【通配】

JavaWeb-Springboot-AOP_第8张图片
JavaWeb-Springboot-AOP_第9张图片
注意:如果要使用一个切入点表示两个不同的方法,可以使用 || 进行连接

在这里插入图片描述
在这里插入图片描述
JavaWeb-Springboot-AOP_第10张图片

7.2 @annotation(注解全类名):根据注解匹配【自定义注解】

JavaWeb-Springboot-AOP_第11张图片
自定义注解

/**
 * 自定义标识注解
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

JavaWeb-Springboot-AOP_第12张图片

在这里插入图片描述

八、连接点

在这里插入图片描述

方法 说明
joinPoint.getTarget().getClass().getName() 获取目标类名
joinPoint.getSignature() 获取目标方法签名
joinPoint.getSignature().getName() 获取目标方法名
joinPoint.getArgs() 获取目标方法运行参数
joinPoint.proceed() 调用原始方法 获取返回值 Object (仅限于环绕通知ProceedingJoinPoint中使用

在这里插入图片描述

八、案例:记录操作日志

在这里插入图片描述

8.1 创建数据库表,封装对应的实体类,并编写插入语句

/*
 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);
}

8.2 自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}

8.3 标注哪些操作需要增加日志

JavaWeb-Springboot-AOP_第13张图片

8.4 定义AOP切面

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;
    }

}

你可能感兴趣的:(JavaWeb,spring,boot,java,spring)