AOP基础
概述
-
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),面向特定方法的编程。
-
动态代理是面向切面编程最主流的实现。
-
SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
入门程序
-
需求
-
统计各个业务层方法的执行耗时
-
-
步骤
-
导入依赖
-
<!-- AOP依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
编写代码
-
package com.testpeople.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; @Component @Slf4j @Aspect //AOP类 public class TimeAspect { @Around("execution(* com.testpeople.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,通知所应用的对象
图解
流程
-
进行开发后,使用的是代理对象,而不是实际对象。
AOP进阶
通知类型
-
@Around:环绕通知,此注解标注的通知在目标方法前,后都被执行。
-
@Before: 前置通知,此注解标注的通知方法在目标方法前被执行。
-
@After: 后置通知,此注解标注的通知方法在目标方法被执行,无论是否异常都会执行。
-
@AfterReturning: 返回后通知,此注解标注的通知方法在,目标方法后被执行,有异常不执行。
-
@AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行。
测试
package com.testpeople.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Aspect
public class MyAspect {
@Before("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
public void before(){
log.info("前置通知");
}
@Around("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("环绕通知"+"before");
Object result = joinPoint.proceed();
log.info("环绕通知"+"after");
return result;
}
@After("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
public void after(){
log.info("后置通知");
}
@AfterReturning("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
public void afterReturning(){
log.info("后置返回通知");
}
@AfterThrowing("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
public void afterThrowing(){
log.info("后置异常通知");
}
}
效果
注意
-
@Around:环绕通知需要自己调用 ProceedingJoinPoint.proceed();
-
@Around:环绕通知方法的返回值,必须指定为Object,来接受原始方法的返回值。
tips
-
切入点表达式抽取
package com.testpeople.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Aspect
public class MyAspect {
@Pointcut("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
private void pt(){
}
//这个方法可以改修订范围,然后被别的包引用。
@Before("pt()")
public void before(){
log.info("前置通知");
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("环绕通知"+"before");
Object result = joinPoint.proceed();
log.info("环绕通知"+"after");
return result;
}
@After("pt()")
public void after(){
log.info("后置通知");
}
@AfterReturning("pt()")
public void afterReturning(){
log.info("后置返回通知");
}
@AfterThrowing("pt()")
public void afterThrowing(){
log.info("后置异常通知");
}
}
通知顺序
-
不同切面类中,默认按照切面类的类名字母排序
-
目标方法前的通知方法:字母排名靠前的先执行。
-
目标方法后的通知方法:字母排名靠前的后执行。
-
-
用@Order(数字)加在切面类上来控制顺序
-
目标方法前的通知方法:数字小的先执行
-
目标方法后的通知方法:数字小的后执行
-
-
概述
-
描述切入点方法的一种表达式
-
-
作用
-
主要用来决定项目中的哪些方法需要加入通知
-
-
常见形式
-
execution(...);根据方法的签名来匹配
-
-
特殊符号
-
如果匹配两个方法,可以使用“||”连接。
-
Tips
-
@annotation(...);根据注解匹配
-
新建注解
-
package com.testpeople.aop; 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 { }
-
在需要添加方法上添加注解,更换切入点表达式。
-
@Around("@annotation(com.testpeople.aop.MyLog)")
-
连接点
-
开发
-
@Around("@annotation(com.testpeople.aop.MyLog)") public Object testJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { //1.获取 目标对象的类名 String className = joinPoint.getTarget().getClass().getName(); log.info(className); //2.获取 目标方法的方法名 String methodName = joinPoint.getSignature().getName(); log.info(methodName); //3.获取 目标方法运行时传入的参数 Object[] methodArgs = joinPoint.getArgs(); log.info(Arrays.toString(methodArgs)); //4.放行 目标方法执行 Object result = joinPoint.proceed(); //5.获取 目标方法的返回值 log.info(result.toString()); return result;//此处可以改变函数的返回结果(添加其他方法) }
效果
AOP案例(操作日志记录功能)
需求
-
将案例中 增、删、改相关的接口的操作日志记录到数据库表中
-
日志信息~操作人、操作时间、执行方法的全类名、执行方法、方法运行时的参数、返回值、方法执行的时长。
思路
步骤
-
准备
-
在案例中引入AOP的依赖
-
<!-- AOP依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
设计好数据库表
-
# 操作日志记录表 create table operate_log( id int unique 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 '操作日志记录表';
-
设计好实体类
-
package com.testpeople.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; @Data @AllArgsConstructor @NoArgsConstructor 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;//耗时 }
-
Mapper接口
-
package com.testpeople.mapper; import com.testpeople.pojo.OperateLog; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; @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})") void insert(OperateLog operateLog); }
-
编码
-
自定义注解
-
package com.testpeople.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 { }
-
定义切面类,完成记录操作日志的逻辑
-
package com.testpeople.aop; import com.alibaba.fastjson.JSONObject; import com.testpeople.mapper.OperateLogMapper; import com.testpeople.pojo.OperateLog; import com.testpeople.utils.JwtUtils; 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 javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.util.Arrays; @Slf4j @Component @Aspect public class LogAspct { @Autowired private HttpServletRequest request; @Autowired private OperateLogMapper operateLogMapper; @Around("@annotation(com.testpeople.anno.Log)") public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { //操作人ID~ 当前员工ID //获取请求头中的jwt令牌中的员工ID String jwt = request.getHeader("token"); //解析 Integer operateUser = (Integer) JwtUtils.parseJwt(jwt).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(); operateLog.setOperateUser(operateUser); operateLog.setOperateTime(operateTime); operateLog.setClassName(className); operateLog.setMethodName(methodName); operateLog.setMethodParams(methodParams); operateLog.setReturnValue(returnValue); operateLog.setCostTime(costTime); operateLogMapper.insert(operateLog); log.info("AOP记录操作日志 {}",operateLog.toString()+"\n"); return result; } }
-
给有需求的类添加@Log注解
-
-
效果
注意
-
获取当前用户
-
从request中获取token 在token中提取当前用户id
-
以上是对SpringBoot框架中的AOP相关的介绍以及简单的使用,通过一个操作记录日志功能进行练习。多点关注、多点爱。(以上知识点笔记来自于小编学习黑马程序员的课程所记录)
项目地址
admin_web_project: 黑马程序员项目javaWebjavaWeb开发学习仓库,前后端分离项目前端Vue后端springboot数据库Mysql