热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBootAOP切面实现

文章目录一、AOP简介二、AOP体系与概念三、AOP实例1、创建SpringBoot工程2、添加依赖3、AOP相关注解3.1、Aspect3.2、Pointcut3.2.1、exe


文章目录

  • 一、AOP简介
  • 二、AOP体系与概念
  • 三、AOP实例
    • 1、创建SpringBoot工程
    • 2、添加依赖
    • 3、AOP相关注解
      • 3.1、@Aspect
      • 3.2、@Pointcut
        • 3.2.1、execution()
        • 3.2.2、annotation()
      • 3.3、@Around
      • 3.4、@Before
      • 3.5、@After
      • 3.6、@AfterReturning
      • 3.7、@AfterThrowing


一、AOP简介

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



那么AOP为何那么重要呢?

在我们的程序中,经常存在一些系统性的需求,比如 权限校验日志记录统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护,那么面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提取出去,便于后期的维护和迭代。




二、AOP体系与概念

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


  • 在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。

  • 在什么时候切入,是业务代码执行前还是执行后。

  • 切入后做什么事,比如做权限校验、日志记录等。

AOP的体系图:
请添加图片描述
一些概念:


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



三、AOP实例


1、创建SpringBoot工程

如何创建详见:IDEA 创建 SpringBoot 项目




2、添加依赖


<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-aopartifactId>
dependency>



3、AOP相关注解

package com.cw.tsb.app.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;&#64;Component
&#64;Aspect
public class ControllerAspect {&#64;Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}&#64;Around("pointCut()")public Object doAround(ProceedingJoinPoint joinPoint) {System.out.println("------------- doAround.");Object obj &#61; null;try {obj &#61; joinPoint.proceed();} catch (Throwable t){t.printStackTrace();}return obj;}&#64;After("pointCut()")public void doAfter(JoinPoint joinPoint){System.out.println("------------- doAfter.");}&#64;Before("pointCut()")public void doBefore(JoinPoint joinPoint){System.out.println("------------- doBefore.");}/*** 后置返回* 如果第一个参数为JoinPoint&#xff0c;则第二个参数为返回值的信息* 如果第一个参数不为JoinPoint&#xff0c;则第一个参数为returning中对应的参数* returning&#xff1a;限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知&#xff0c;否则不执行&#xff0c;* 参数为Object类型将匹配任何目标返回值*/&#64;AfterReturning(value &#61; "pointCut()", returning &#61; "result")public void doAfterReturning(JoinPoint joinPoint, String result){System.out.println("doAfterReturning result &#61; " &#43; result);}&#64;AfterThrowing(value &#61; "pointCut()", throwing &#61; "t")public void doAfterThrowing(JoinPoint joinPoint, Throwable t){System.out.println("------------- doAfterThrowing throwable &#61; " &#43; t.toString());}
}



3.1、&#64;Aspect

该注解要添加在类上&#xff0c;声明这是一个切面类&#xff0c;使用时需要与&#64;Component注解一起用&#xff0c;表明同时将该类交给spring管理。

&#64;Component
&#64;Aspect
public class ControllerAspect {
}



3.2、&#64;Pointcut

用来定义一个切点&#xff0c;即上文中所关注的某件事情的入口&#xff0c;切入点定义了事件触发时机。

该注解需要添加在方法上&#xff0c;该方法签名必须是 public void 类型&#xff0c;可以将&#64;Pointcut 中的方法看作是一个用来引用的助记符&#xff0c;因为表达式不直观&#xff0c;因此我们可以通过方法签名的方式为此表达式命名。因此 &#64;Pointcut 中的方法只需要方法签名&#xff0c;而不需要在方法体内编写实际代码

该注解有两个常用的表达式&#xff1a;execution()annotation()




3.2.1、execution()

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}
}

表达式为&#xff1a;

execution(* com.cw.tsb.app.controller..*.*(..))

  • 第一个 * &#xff1a;表示返回值类型&#xff0c;* 表示所有类型&#xff1b;

  • 包名&#xff1a;标识需要拦截的包名&#xff1b;

  • 包名后的 ..&#xff1a;表示当前包和当前包的所有子包&#xff0c;在本例中指 com.cw.tsb.app.controller 包、子包下所有类&#xff1b;

  • 第二个 * &#xff1a;表示类名&#xff0c;* 表示所有类&#xff1b;

  • 最后的 *(..) &#xff1a;星号表示方法名&#xff0c;* 表示所有的方法&#xff0c;后面括弧里面表示方法的参数&#xff0c;两个句点表示任何参数。




3.2.2、annotation()

annotation() 方式是针对某个注解来定义切点&#xff0c;比如我们对具有 &#64;PostMapping 注解的方法做切面&#xff0c;可以如下定义切面&#xff1a;

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("&#64;annotation(org.springframework.web.bind.annotation.PostMapping)")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}
}

然后使用该切面的话&#xff0c;就会切入注解是 &#64;PostMapping 的所有方法。这种方式很适合处理 &#64;GetMapping&#64;PostMapping&#64;DeleteMapping不同注解有各种特定处理逻辑的场景。

还有就是如上面案例所示&#xff0c;针对自定义注解来定义切面。

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("&#64;annotation(com.cw.tsb.app.annotation.PermissionsAnnotation)")private void permissionCheck() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}
}



3.3、&#64;Around

&#64;Around 注解用于修饰 Around 增强处理&#xff0c;Around增强处理非常强大&#xff0c;表现在&#xff1a;


  • &#64;Around 可以自由选择增强动作与目标方法的执行顺序&#xff0c;也就是说可以在增强动作前后&#xff0c;甚至过程中执行目标方法。这个特性的实现在于&#xff0c;调用 ProceedingJoinPoint 参数的 procedd() 方法才会执行目标方法。

  • &#64;Around 可以改变执行目标方法的参数值&#xff0c;也可以改变执行目标方法之后的返回值。

Around 增强处理有以下特点&#xff1a;


  • 当定义一个 Around 增强处理方法时&#xff0c;该方法的第一个形参必须是 ProceedingJoinPoint 类型&#xff08;至少一个形参&#xff09;。在增强处理方法体内&#xff0c;调用 ProceedingJoinPointproceed 方法才会执行目标方法&#xff1a;这就是 &#64;Around 增强处理可以完全控制目标方法执行时机、如何执行的关键&#xff1b;如果程序没有调用 ProceedingJoinPointproceed 方法&#xff0c;则目标方法不会执行。

  • 调用 ProceedingJoinPointproceed 方法时&#xff0c;还可以传入一个 Object[] 对象&#xff0c;该数组中的值将被传入目标方法作为实参 —— 这就是 Around 增强处理方法可以改变目标方法参数值的关键。这就是如果传入的 Object[] 数组长度与目标方法所需要的参数个数不相等&#xff0c;或者 Object[] 数组元素与目标方法所需参数的类型不匹配&#xff0c;程序就会出现异常。

&#64;Around 功能虽然强大&#xff0c;但通常需要在线程安全的环境下使用。因此&#xff0c;如果使用普通的&#64;Before&#64;AfterReturning 就能解决的问题&#xff0c;就没有必要使用 Around 了。如果需要目标方法执行之前和之后共享某种状态数据&#xff0c;则应该考虑使用 Around 。尤其是需要使用增强处理阻止目标的执行&#xff0c;或需要改变目标方法的返回值时&#xff0c;则只能使用 Around 增强处理了。




3.4、&#64;Before

&#64;Before 注解指定的方法在切面切入目标方法之前执行&#xff0c;可以做一些 Log 处理&#xff0c;也可以做一些信息的统计&#xff0c;比如 获取用户的请求 URL 以及 用户的 IP 地址等等&#xff0c;这个在做个人站点的时候都能用得到&#xff0c;都是常用的方法。例如下面代码&#xff1a;

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}/*** 在上面定义的切面方法之前执行该方法* &#64;param joinPoint jointPoint*/&#64;Before("pointCut()")public void doBefore(JoinPoint joinPoint) {// 获取签名Signature signature &#61; joinPoint.getSignature();// 获取切入的包名String declaringTypeName &#61; signature.getDeclaringTypeName();// 获取即将执行的方法名String funcName &#61; signature.getName();log.info("即将执行方法为: {}&#xff0c;属于{}包", funcName, declaringTypeName);// 也可以用来记录一些信息&#xff0c;比如获取请求的 URL 和 IPServletRequestAttributes attributes &#61; (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request &#61; attributes.getRequest();// 获取请求 URLString url &#61; request.getRequestURL().toString();// 获取请求 IPString ip &#61; request.getRemoteAddr();}
}

JointPoint 对象很有用&#xff0c;可以用它来获取一个签名&#xff0c;利用签名可以获取请求的包名、方法名&#xff0c;包括参数&#xff08;通过 joinPoint.getArgs() 获取&#xff09;等。




3.5、&#64;After

&#64;After 注解和 &#64;Before 注解相对应&#xff0c;指定的方法在切面切入目标方法之后执行&#xff0c;也可以做一些完成某方法之后的 Log 处理。

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}/*** 在上面定义的切面方法之后执行该方法* &#64;param joinPoint jointPoint*/&#64;After("pointCut()")public void doAfter(JoinPoint joinPoint) {log.info("&#61;&#61;&#61;&#61; doAfter 方法进入了&#61;&#61;&#61;&#61;");Signature signature &#61; joinPoint.getSignature();String method &#61; signature.getName();log.info("方法{}已经执行完", method);}
}

到这里&#xff0c;我们来写个 Controller 测试一下执行结果&#xff0c;新建一个 AopController 如下&#xff1a;

&#64;RestController
&#64;RequestMapping("/aop")
public class AopController {&#64;GetMapping("/{name}")public String testAop(&#64;PathVariable String name) {return "Hello " &#43; name;}
}

启动项目&#xff0c;在浏览器中输入&#xff1a;http://localhost:8080/aop/csdn&#xff0c;观察一下控制台的输出信息&#xff1a;

&#61;&#61;&#61;&#61;doBefore 方法进入了&#61;&#61;&#61;&#61;
即将执行方法为: testAop&#xff0c;属于com.itcodai.mutest.AopController
用户请求的 url 为&#xff1a;http://localhost:8080/aop/name&#xff0c;ip地址为&#xff1a;0:0:0:0:0:0:0:1
&#61;&#61;&#61;&#61; doAfter 方法进入了&#61;&#61;&#61;&#61;
方法 testAop 已经执行完

从打印出来的 Log 中可以看出程序执行的逻辑与顺序&#xff0c;可以很直观的掌握 &#64;Before&#64;After 两个注解的实际作用。




3.6、&#64;AfterReturning

&#64;AfterReturning 注解和 &#64;After 有些类似&#xff0c;区别在于 &#64;AfterReturning 注解可以用来捕获切入方法执行完之后的返回值&#xff0c;对返回值进行业务逻辑上的增强处理&#xff0c;例如&#xff1a;

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}/*** 后置返回* 如果第一个参数为JoinPoint&#xff0c;则第二个参数为返回值的信息* 如果第一个参数不为JoinPoint&#xff0c;则第一个参数为returning中对应的参数* returning&#xff1a;限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知&#xff0c;否则不执行&#xff0c;* 参数为Object类型将匹配任何目标返回值*/&#64;AfterReturning(value &#61; "pointCut()", returning &#61; "result")public void doAfterReturning(JoinPoint joinPoint, String result){// 实际项目中可以根据业务做具体的返回值增强}
}

需要注意的是&#xff0c;在 &#64;AfterReturning 注解 中&#xff0c;属性 returning 的值必须要和参数保持一致&#xff0c;否则会检测不到。该方法中的第二个入参就是被切方法的返回值&#xff0c;在 doAfterReturning 方法中可以对返回值进行增强&#xff0c;可以根据业务需要做相应的封装。




3.7、&#64;AfterThrowing

当被切方法执行过程中抛出异常时&#xff0c;会进入 &#64;AfterThrowing 注解的方法中执行&#xff0c;在该方法中可以做一些异常的处理逻辑。要注意的是 throwing 属性的值必须要和参数一致&#xff0c;否则会报错。该方法中的第二个入参即为抛出的异常。

&#64;Aspect
&#64;Component
public class ControllerAspect {&#64;Pointcut("execution(* com.cw.tsb.app.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法&#xff0c;而不做任何特殊的处理。}&#64;AfterThrowing(value &#61; "pointCut()", throwing &#61; "t")public void doAfterThrowing(JoinPoint joinPoint, Throwable t){System.out.println("------------- doAfterThrowing throwable &#61; " &#43; t.toString());// 处理异常的逻辑}
}




推荐阅读
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 这篇文章主要介绍了Python拼接字符串的七种方式,包括使用%、format()、join()、f-string等方法。每种方法都有其特点和限制,通过本文的介绍可以帮助读者更好地理解和运用字符串拼接的技巧。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 面向对象之3:封装的总结及实现方法
    本文总结了面向对象中封装的概念和好处,以及在Java中如何实现封装。封装是将过程和数据用一个外壳隐藏起来,只能通过提供的接口进行访问。适当的封装可以提高程序的理解性和维护性,增强程序的安全性。在Java中,封装可以通过将属性私有化并使用权限修饰符来实现,同时可以通过方法来访问属性并加入限制条件。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
author-avatar
花都色魔l文龙l_419
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有