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












![[米联客-安路飞龙DR1-FPSOC] SDK入门篇连载-09 PL AXI-GPIO实验](https://i-blog.csdnimg.cn/direct/27274363a7bd455f92d149835d954751.png)






