1 理解AOP

1.1 什么是AOP

AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。

那么AOP为何那么重要呢?在我们的程序中,经常存在一些系统性的需求,比如权限校验、日志记录、统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护。例如下面这个示意图:
有多少业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然,用面向对象的思想,我们可以把这些重复的代码抽离出来,写成公共方法,就是下面这样:
这样,代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。有没有更好的方式呢?有的,那就是AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中:

1.2 AOP体系与概念

简单地去理解,其实AOP要做三类事:

在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。
在什么时候切入,是业务代码执行前还是执行后。
切入后做什么事,比如做权限校验、日志记录等。
因此,AOP的体系可以梳理为下图:
一些概念详解:

  • Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
  • Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
  • Aspect:切面,即Pointcut和Advice。
  • Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
  • Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

2 AOP实例

实践出真知,接下来我们就撸代码来实现一下AOP。

  • 使用 AOP,首先需要引入 AOP 的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.1 第一个实例

接下来,我们先看一个极简的例子:所有的get请求被调用前在控制台输出一句"get请求的advice触发了"。

具体实现如下:

  • 1.创建一个AOP切面类,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现advice:
package com.jingudi.framework.log.log.aspect;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;/*** @author 1060785272@qq.com* @version 1.0* @description: TODO* @date 2022-4-10 下午 9:31*/
@Aspect
@Component
public class LogAdvice {// 定义一个切点:所有被GetMapping注解修饰的方法会织入advice@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")private void logAdvicePointcut(){}@Before("logAdvicePointcut()")public void logAdvice(){// 这里只是一个示例,你可以写任何处理逻辑System.out.println("get请求的advice触发了");}
}
  • 2.随便创建一个接口类,内部创建一个get请求
    (必须要有@GetMapping):
@ApiOperation("查询用户列表")
@GetMapping
public PageResult<UserEntity> getUserList(QueryUserVo vo) {return userService.getUserList(vo);
}

2.2 第二个实例

下面我们将问题复杂化一些,该例的场景是:

自定义一个注解PermissionsAnnotation
创建一个切面类,切点设置为拦截所有标注PermissionsAnnotation的方法,截取到接口的参数,进行简单的权限校验
将PermissionsAnnotation标注在测试接口类的测试接口test上
具体的实现步骤:

  • 1.使用@Target、@Retention、@Documented自定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{}
  • 2.创建第一个AOP切面类,,只要在类上加个@Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现第一步权限校验逻辑:
package com.jingudi.advice;import com.alibaba.fastjson.JSONObject;
import com.jingudi.modules.system.dto.DictDetailDto;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;/*** @author 1060785272@qq.com* @version 1.0* @description: TODO* @date 2022-4-10 下午 9:56*/
@Aspect
@Component
@Order(1)
@Slf4j
public class PermissionFirstAdvice {// 定义一个切面,括号内写入第1步中自定义注解的路径@Pointcut("@annotation(com.jingudi.annotation.PermissionsAnnotation)")private void permissionCheck() {}@Before("permissionCheck()")public void beforeAdvice(JoinPoint joinPoint){// 这里只是一个示例,你可以写任何处理逻辑System.out.println("---------Before触发了----------");// 获取签名Signature signature = joinPoint.getSignature();// 获取切入的包名String declaringTypeName = signature.getDeclaringTypeName();// 获取即将执行的方法名String funcName = signature.getName();log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);// 也可以用来记录一些信息,比如获取请求的 URL 和 IPServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 获取请求 URLString url = request.getRequestURL().toString();// 获取请求 IPString ip = request.getRemoteAddr();log.info("用户请求的url为:{},ip地址为:{}", url, ip);}@Around("permissionCheck()")public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {// 这里只是一个示例,你可以写任何处理逻辑System.out.println("---------Around触发了----------");//获取请求参数,详见接口类Object[] objects = joinPoint.getArgs();System.out.println(objects);
//        Integer id = ((JSONObject) objects[0]).getInteger("id");DictDetailDto object1 = (DictDetailDto)objects[0];// 修改入参JSONObject object = new JSONObject();return joinPoint.proceed(objects);}@AfterReturning(pointcut  = "permissionCheck()", returning = "result")public void afterReturningAdvice(JoinPoint joinPoint, Object result){// 这里只是一个示例,你可以写任何处理逻辑System.out.println("---------AfterReturning触发了----------");Signature signature = joinPoint.getSignature();String classMethod = signature.getName();log.info("方法{}执行完毕,返回参数为:{}", classMethod, result);// 实际项目中可以根据业务做具体的返回值增强log.info("对返回参数进行业务上的增强:{}", result + "增强版");}@AfterThrowing(pointcut = "permissionCheck()", throwing = "ex")public void afterThrowing(JoinPoint joinPoint, Throwable ex) {Signature signature = joinPoint.getSignature();String method = signature.getName();// 处理异常的逻辑log.info("执行方法{}出错,异常为:{}", method, ex);}@After("permissionCheck()")public void afterAdvice(JoinPoint joinPoint){// 这里只是一个示例,你可以写任何处理逻辑System.out.println("---------After触发了----------");Signature signature = joinPoint.getSignature();String method = signature.getName();log.info("方法{}已经执行完", method);}
}

@Order(0)
AOP加载顺序(切面加载顺序)

总结:

  1. 前置通知
    在目标方法执行之前执行执行的通知。

  2. 环绕通知
    在目标方法执行之前和之后都可以执行额外代码的通知。

  3. 后置通知
    在目标方法执行之后执行的通知。

  4. 异常通知
    在目标方法抛出异常时执行的通知。

  5. 最终通知
    是在目标方法执行之后执行的通知。

以上5种都可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。

五种通知的执行顺序:

  1. 在目标方法没有抛出异常的情况下
    前置通知
    环绕通知的调用目标方法之前的代码
    目标方法
    环绕通知的调用目标方法之后的代码
    后置通知
    最终通知

  2. 在目标方法抛出异常的情况下
    前置通知
    环绕通知的调用目标方法之前的代码
    目标方法
    抛出异常
    异常通知
    最终通知

  3. 如果存在多个切面
    多切面执行时,采用了责任链设计模式。
    切面的配置顺序决定了切面的执行顺序,多个切面执行的过程,类似于方法调用的过程,在环绕通知的proceed()执行时,
    去执行下一个切面或如果没有下一个切面执行目标方法,从而达成了如下的执行过程:

Spring AOP代码实现:实例演示与注解全解相关推荐

  1. SpringBoot:切面AOP实现权限校验:实例演示与注解全解

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 目录 理解AOP 什么是AOP AOP体系与概念 AOP实例 第一个实例 ...

  2. 切面AOP实现权限校验:实例演示与注解全解(强烈推荐)

    点击关注公众号,利用碎片时间学习 1 理解AOP 1.1 什么是AOP AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IO ...

  3. 【SpringBoot-3】切面AOP实现权限校验:实例演示与注解全解

    SpringBoot中的AOP处理 1 理解AOP 1.1 什么是AOP 1.2 AOP体系与概念 2 AOP实例 2.1 第一个实例 2.2 第二个实例 3 AOP相关注解 3.1 @Pointcu ...

  4. spring aop代码的增强

    这篇博客,主要会分析spring aop是如何实现代码增强的. 从上一篇博客 我们大概知道,spring能在不改变代码的前提下,往一个方法的之前和之后添加代码. 想下,java中有哪种技术可以帮我们实 ...

  5. 详解 | Spring Boot 最核心的 3 个注解详解

    Hi !我是小小,开始本周的第一篇,本周第一篇内容是关于Spring Boot 最核心的三个注解,将会对这三个注解进行详细解释. 前言 Spring Boot 最大的特点是无需 XML 配置文件,能够 ...

  6. Spring Boot 最核心的 3 个注解详解

    最近面试一些 Java 开发者,他们其中有些在公司实际用过 Spring Boot, 有些是自己兴趣爱好在业余自己学习过. 然而,当我问他们 Spring Boot 最核心的 3 个注解是什么,令我失 ...

  7. Spring aop 记录操作日志 Aspect 自定义注解

    时间过的真快,转眼就一年了,没想到随手写的笔记会被这么多人浏览,不想误人子弟,于是整理了一个优化版,在这里感谢智斌哥提供的建议和帮助,话不多说,进入正题 所需jar包 :spring4.3相关联以及a ...

  8. Spring Aop(四)——基于Aspectj注解的Advice介绍

    4 基于Aspectj注解的Advice介绍 之前介绍过,Advice一共有五种类型,分别是before.after return.after throwing.after(finally)和arou ...

  9. IntelliJ IDEA绑定GitHub实现代码版本控制实例演示,IDEA上传、更新、同步项目到GitHub演示,Git的下载与安装

    IDEA 绑定 GitHub 实现代码版本控制 第一章:IDEA 配置 Git 并绑定 GitHub ① 下载 Git ① 安装 Git ③ 设置 Git 的用户名和用户邮箱 ④ IEDA 配置 Gi ...

最新文章

  1. BZOJ 1014 火星人prefix
  2. 【Python教程】删除字符串中字符的四种方法
  3. PG SQL数据库读写分离的思路
  4. OpenGL之macOS上的环境搭建
  5. 80端口攻击_内网端口转发工具的使用总结
  6. 关于UAC执行级别的研究
  7. 升级项目到.NET Core 2.0,在Linux上安装Docker,并成功部署
  8. 修改路由器mac地址_你知道吗:路由器转发报文时,会剥掉MAC地址,重新封装
  9. 从零基础入门Tensorflow2.0 ----八、39.3. gpu3
  10. 第4讲 | 区块链的应用类型
  11. 数列极限四则运算误区
  12. N子棋的实现方法,包括三子棋,五子棋
  13. 为activity设置主题theme
  14. 12306所有车次及时刻表的爬取中
  15. android 文字转化为图片格式,Android 文字生成图片
  16. 叶念琛告诉你什么是爱情。。。
  17. 涂鸦Wi-FiBLE SoC开发幻彩灯带(5)----烧录授权
  18. 解决异常-ORA-01747 invalid user.table.column, table.column, or column specification
  19. md文件转换word文档
  20. 阿里巴巴云游戏(元境)春季2023届校园招聘正式开启

热门文章

  1. English Learning - L2 语音作业打卡 辅音唇齿音 [f] [v] Day28 2023.3.20 周一
  2. 建木(Jianmu)----使用docker-compose安装部署Jianmu(建木)
  3. C# 生成软件注册码
  4. 2021年中国便携式净水器市场趋势报告、技术动态创新及2027年市场预测
  5. 2019年vipkid最新评价,vipkid多少钱一节课
  6. 谈谈孩子第一次在VIPkid学半年的真实感受
  7. Proe 5.0鼠标滚轮无法缩放的解决方法
  8. 什么汤晚上千万不能喝
  9. 解决百度网盘等软件无法联网但是浏览器可以上网的问题
  10. 书法拓片matlab,基于MATLAB实现石刻浮雕图像-数字拓片-技术的研究