06- AOP(实现案例:记录日志操作)

文章介绍了SpringBoot中的AOP(面向切面编程)应用,用于统计业务方法的执行耗时。通过添加AOP依赖,定义切面类和环绕通知,记录方法开始和结束时间来计算耗时。此外,还展示了如何自定义注解和切入点表达式来匹配需要监控的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 通知类型

2. 通知顺序 

3. 切入点表达式

execution()

annotation() 

 4. 连接点(JoinPoint) 

5. 案例:将CRUD接口的相关操作记录到数据库中


AOP: Aspect Oriented Programming (面向切面编程、面向方面编程),其实就是面向特定方法编程 

优势:代码无侵入、减少重复代码、提高开发效率、维护方便

环境准备

以下代码是从tlias案例中复制过来的

场景

        1. 案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方 法的执行耗时
                 导入依赖:

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

                定义Aop函数:

package pearl.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
public class TimeAspect {
    @Around("execution(* pearl.service.*.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 记录开始时间
        long begin =System.currentTimeMillis();
        // 2. 调用原始方法运行
        Object result = joinPoint.proceed();
        // 3. 记录结束时间,计算方法执行耗时
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);
        return result;
    }
}

 完成!

核心概念:

连接点: JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)

切入点: PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用

切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
目标对象: Target,通知所应用的对象

1. 通知类型

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
    • 需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
    • 方法的返回值必须指定为object,来接收原始方法的返回值。
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing :异常后通知,此注解标注的通知方法发生异常后执行

补充:@Pointcut 

        该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。

2. 通知顺序 

3. 切入点表达式

概念:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
        1.execution(..…)︰根据方法的签名来匹配                                                                                        2.@annotation(...) ︰根据注解匹配

execution()

annotation() 

        1. 自定义注解:

package pearl.annotation;

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 MyLog {
}

        2. 在方法前加上自定义的注解

         3. 根据注解匹配切入点表达式 

 4. 连接点(JoinPoint) 

5. 案例:将CRUD接口的相关操作记录到数据库中

日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。

步骤:

  • 引入AOP依赖(该文上方有,自行查看)
  • 准备数据库表,并引入实体类
  • 自定义注解@Log
  • 定义切面类,完成记录操作日志的逻辑

数据库表:

-- 操作日志表
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 '操作日志表';

实体类OperateLog:

@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; //操作耗时
}

OperateLogMapper接口:

@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

定义注解类:

首先创建一个包anno

然后在包里新建Annotation(注解类)Log

@Retention(RetentionPolicy.RUNTIME) //指定运行时生效
@Target(ElementType.METHOD)   //指定该注解作用到方法上
public @interface Log {
}

定义切面类 LogAspect

package pearl.aop;

import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
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 pearl.mapper.OperateLogMapper;
import pearl.pojo.OperateLog;
import pearl.utils.JwtUtils;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;

@Slf4j
@Component  //标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>)
@Aspect  //切面类
public class LogAspect {
    @Autowired
    private OperateLogMapper operateLogMapper;  //要调用该类中的insert方法

    @Autowired
    private HttpServletRequest request;

    @Around("@annotation(pearl.anno.Log)")//@annotation表示匹配注解类,括号中是要匹配注解的全类名,表示使用该注解的方法都会匹配
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {

//        获取记录到日志中的信息
        // 操作人ID
             // 获取请求头中的jwt令牌,解析令牌
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer operateUserId = (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 = new OperateLog(null,operateUserId,operateTime,className,methodName,methodParams,returnValue,costTime);
        
        operateLogMapper.insert(operateLog);

        return result;
    }
}

 在需要的controller接口方法前设置@Log注解即可。(切记不要在登录方法前加)

        

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值