一.什么是AOP?
AOP称为面向切面编程,用于将那些与业务无关,但又对多个对象产生公共行为影响的代码,抽取并封装成一个可用的模块,这个模块被命名为切面。
二.AOP的作用和常用场景
作用:减少重复代码,降低模块之间的耦合程度,同时提高了系统的可维护性
常用场景:1.记录操作日志
2.缓存处理
3.spring内置的事务处理
三.使用方法
1.添加依赖
<!-- Aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.添加@Conponent和@Aspect注解
//统计调用方法的使用时间
@Slf4j
@Component
@Aspect //AOP类
public class TimeAspect {
@Around("execution(* com.itheima.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;
}
}
3.通知类型(5种类型)
我们常用的就是@Around类型通知,
@Pointcut:该注解的作用是将公共的切点表达式抽取出来,需要到时引用该切点表达式即可
//切点,指定重复的切入点表达式,引用时,引用所属的方法即可,如@Around("pt()")
如:
@Pointcut("execution(*com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){}
4.通知顺序
//系统默认
@before:类名字母排序越靠前越先执行
@after:类名字母排序越靠后越先执行
//可以用@Order注解给每一个通知加上优先级,指定数字
如@Order(5)
@before:数字小的先执行
@after:数字大的先执行
5.切点表达式
作用:主要是用来决定项目中的哪些方法需要加入通知
常见形式:1.execution(.....) 根据方法的签名来匹配
2.@annotation(....):根据注解匹配
//如果要执行两个切点表达式可以用逻辑运算符与或非进行判断
@Pointcut("execution(* com.itheima.service.DeptService.list()) || " +
"execution(* com.itheima.service.DeptService.delete(java.lang.Integer))")
@Annotation
1.先自己创建一个注解,如Mylog
package com.wpk.anno;
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 {
}
2.在自己想通知的实现方法上添加上,自己创建的注解,如@Mylog,一般放在Controller方法上
3.引用切点表达式,如Around("@annotation(com.itheima.aop.Mylog)")
6.连接点
7.AOP案例
1.创建数据库文件
2.创建数据库文件对应的实体类
3.创建自定义注解,并在Controller方法上添加注解
4.定义Mapper往数据库添加信息
5.创建aop切面类,如下面所示
package com.wpk.aop;
import com.alibaba.fastjson.JSONObject;
import com.wpk.mapper.OperateLogMapper;
import com.wpk.pojo.OperateLog;
import com.wpk.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 LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.wpk.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 operatTime=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();
//1.调用原始目标方法运行
Object result=joinPoint.proceed();
//方法返回值
String returnValue=JSONObject.toJSONString(result);
//结束时间
Long end=System.currentTimeMillis();
//操作耗时
Long costTime=end-begin;
//2.记录操作日志
OperateLog operateLog=new OperateLog(null,operateUser,operatTime,className,methodName,methodParams,returnValue,costTime);
operateLogMapper.insert(operateLog);
return result;
}
}