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;//IDprivate Integer operateUser;//操作人IDprivate 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 {@Autowiredprivate HttpServletRequest request;@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(com.testpeople.anno.Log)")public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人ID~ 当前员工ID//获取请求头中的jwt令牌中的员工IDString 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