Javaweb学习笔记——后端实战7 springAOP

1、概念

使用AOP:

步骤:

之后导入一个文件,只保留了部门的增删查改功能

之后在包下新建一个类:

并输入代码:

复制代码
package com.itheima.apo;

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//标识当前是一个AOP类
public class RecordTimeAspect {
    @Around("execution(* com.itheima.service.impl.*.*(..))")//当前AOP对业务层的所有类和所有方法生效
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //记录方法运行时间
        long start = System.currentTimeMillis();
        //原始方法执行
        Object result=joinPoint.proceed();
        //记录方法结束时间并计算执行耗时
        long end = System.currentTimeMillis();
        log.info("方法运行时间:{}ms",end-start);
        return result;
    }
}

之后启动项目,并使用增改删的功能,得到如下结果:

但是当前的结果并不确定哪个方法使用了多少秒,之后在代码中新增一个方法的占位符:

复制代码
log.info("方法:{} 运行时间:{}ms",joinPoint.getSignature(),end-start);

结果如下所示:

可以通过更改注解Around来修改当前AOP代码对那部分生效!

总结:

AOP的核心概念:

AOP通知类型:

(1)前置通知

(2)环绕通知

(3)后置通知

(4)返回后通知

(5)异常后通知

在实际代码中进行更改,将公共的切入点表达式用注解进行提取,之后重命名为pt(),将下面的表达式一一进行更换!

注解的作用域:

总结:

这个顺序就像栈一样,"先进后出",譬如命名分别以a、b、c为首字母

目标方法通知前的执行顺序为:a、b、c

目标方法通知后的执行顺序为:c、b、a

但是在实际中,aspect的命名是和业务有关系的,但此时以默认的字母排序就不是很合适,因此,添加新注解:

譬如:order中的值是相对值,只有和其他的aspect相比数字是最小的,就先执行这个!

切入点表达式:对于访问修饰符(publlic、private等)和异常方法等可以省略

..表示任意个,相当于不论是1个还是2个还是3个都代替了!

del*表示匹配的方法名必须是以del开头的方法,后边任意,表明*还可以表示字母的一部分,相当于模糊匹配!!!

如今要对下面两个进行描述切入点表达式:

"execution(* com.itheima.service.impl.DeptServiceImpl.list(..))||"+"execution(* com.itheima.service.impl.DeptServiceImpl.delete(..))"

可以使用逻辑运算符进行匹配!

总结:

以上切入点表达式一般适用于普通的查询,对于特殊的查询使用逻辑运算符有些繁琐,因此在这里使用第二种写法:

新建一个包并新建一个注解类:

之后在方法上新增一个描述注解的注解:表明以下只能在方法上生效!

之后再加一个注解说明注解什么时候生效:

复制代码
@Retention(RetentionPolicy.RUNTIME)//表明在运行时生效

以下是这个类的所有代码:

复制代码
package com.itheima.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

@Target(ElementType.METHOD)//表明这个注解只能加在方法上
@Retention(RetentionPolicy.RUNTIME)//表明在运行时生效
public @interface LogOperation {
}

之后在aspect文件中修改切入点表达式为上述类的位置:

复制代码
@Before("@annotation(com.itheima.anno.LogOperation)")
public void before(){
    log.info("方法开始执行");
}

最后在需要调用的方法上添加注解LogOperation即可,如下所示:

复制代码
@LogOperation
@Override
public List<Dept> list() {
    List<Dept> deptList = deptMapper.list();
    return deptList;
}

@LogOperation
@Override
public void delete(Integer id) {
    deptMapper.delete(id);
}

此时在aspect文件中就可以匹配到相关的方法!

如果是使用环绕通知,需要使用proceedingjoinpoint来调用,如果是其余的使用joinpoint即可!!!

AOP的底层是动态代理技术!

之后在tilas中添加以下功能:

首先在数据库中添加表:

复制代码
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_emp_id int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(2000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint unsigned comment '方法执行耗时,单位:ms'
) comment '操作日志表';

之后在tilas案例的代码中创建相应的实体类,放在pojo软件包中:

复制代码
package com.itheima.pojo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class OperateLog {
    private Integer id; //ID
    private Integer operateEmpId; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}

之后在mapper中添加以下代码去实现:

复制代码
package com.itheima.mapper;

import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OperateLogMapper {

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

}

之后在相应的pom文件中添加AOP依赖:

复制代码
<!-- AOP起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

之后新建一个anno包,并存放一个注解:

代码如下:

复制代码
package com.itheima.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)//表明这个注解只能加在方法上
@Retention(RetentionPolicy.RUNTIME)//表明在运行时生效
public @interface Log {
}

alt+鼠标左键可以同时更改同一列,如下所示!

之后新建包和类:

代码如下:

复制代码
package com.itheima.aop;

import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
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.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;

@Slf4j
@Aspect
@Component
public class OperationLogAspect {
    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        //执行目标方法
        Object result = joinPoint.proceed();

        //计算耗时
        long end = System.currentTimeMillis();
        long costTime = end - start;

        //构建日志实体
        OperateLog Log = new OperateLog();
        Log.setOperateEmpId(getCurrentUserId()); // 这里需要你根据实际情况获取当前用户ID
        Log.setOperateTime(LocalDateTime.now());
        Log.setClassName(joinPoint.getTarget().getClass().getName());
        Log.setMethodName(joinPoint.getSignature().getName());
        Log.setMethodParams(Arrays.toString(joinPoint.getArgs()));
        Log.setReturnValue(result != null ? result.toString() : "void");
        Log.setCostTime(costTime);

        // 保存日志
        log.info("操作日志:{}",Log);
        operateLogMapper.insert(Log);

        return result;
    }
    private Integer getCurrentUserId() {
        return 1;
    }
}

先将getCurrentUserId的返回值暂定为1,之后再更改!

之后在controller中的部门管理中进行修改,以部门的增删改为例,在方法前添加@Log注解:

之后回到aspect文件中,点击图标可得以下三个方法已经绑定AOP方法

之后启动项目在前端的页面中打开:

对部门进行增改删的操作,之后查看数据库是否存入数据:

以上数据表明成功实现日志记录!

接下来要对userid进行优化,上面我们返回的是1这并不符合实际!

threadLocal:特点是在哪个线程存的只能在那个线程取!

获取用户id步骤:

首先在utils包中新增一个类:

代码如下:

复制代码
package com.itheima.utils;

public class CurrentHolder {

    private static final ThreadLocal<Integer> CURRENT_LOCAL = new ThreadLocal<>();

    public static void setCurrentId(Integer employeeId) {
        CURRENT_LOCAL.set(employeeId);
    }

    public static Integer getCurrentId() {
        return CURRENT_LOCAL.get();
    }

    public static void remove() {
        CURRENT_LOCAL.remove();
    }
}

之后在解析令牌的代码中更改解析后令牌,还需要通过threadlocal存值并最后放行之后删除存入的值:

复制代码
 //检验令牌,如果不通过响应401
    try{
        Claims claims=JwtUtils.parseJWT(token);
        Integer empId=Integer.valueOf(claims.get("id").toString());
        CurrentHolder.setCurrentId(empId);
        log.info("当前用户id为:{},将其存入threadlocal",empId);
    }catch(Exception e){
        response.setStatus(401);
        return ;
    }

    //校验通过则放行
    log.info("令牌校验通过");
    filterChain.doFilter(request,response);
    CurrentHolder.remove();//删除threadlocal中的数据
}

最后在AOP类中更改返回的userid:

复制代码
private Integer getCurrentUserId() {
    return CurrentHolder.getCurrentId();
}

演示效果:首先使用songjiang,123456进行登录

之后进行增改删的操作,在数据库中进行更改:

查阅数据库之后证明代码更改无误:

总结:

相关推荐
来两个炸鸡腿2 小时前
【Datawhale组队学习202601】Base-NLP task06 大模型训练与量化
人工智能·学习·自然语言处理
消失的旧时光-19432 小时前
第九课实战版:异常与日志体系 —— 后端稳定性的第一道防线
java·后端
bylander2 小时前
【AI学习】TM Forum自智网络L4级标准体系
人工智能·学习·智能体·自动驾驶网络
我想我不够好。2 小时前
2026.1.28 消防监控学习
学习
Engineer邓祥浩2 小时前
设计模式学习(24) 23-22 策略模式
学习·设计模式·策略模式
Hammer_Hans2 小时前
DFT笔记26
笔记
2601_949720262 小时前
flutter_for_openharmony手语学习app实战+手语识别实现
学习·flutter
小程同学>o<2 小时前
嵌入式之C/C++(二)内存
c语言·开发语言·c++·笔记·嵌入式软件·面试题库
浅念-2 小时前
C语言——内存函数
c语言·经验分享·笔记·学习·算法