仓库地址

w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)

Chapter05-SpringBoot中的AOP面向切面编程简介

在上一章中,我们编写了一款基于SpringBoot的书籍信息管理Web应用,实现了对书籍信息的增删查改操作。现在,我们有了一个新的需求:为了方便后台服务监管我们的Web服务的请求耗时,我们需要增强一下对我们的Web应用,希望对每个请求都能够打印一下处理耗时。

基本方式

为了实现这样的需求,我们首先以获取指定ID的书籍信息这个API为例,开始进行编程:

  1. @GetMapping("{id}")
  2. public Book getBookById(@PathVariable("id") String id) {
  3. long currentTimeMillis = System.currentTimeMillis();
  4. try {
  5. // 为了更加明显,我模拟了一个耗时
  6. Thread.sleep(500);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. Optional<Book> first = this.bookList
  11. .stream()
  12. .filter(b -> b.getId().equals(id))
  13. .findFirst();
  14. System.out.printf("处理耗时:%d ms %n", (System.currentTimeMillis() - currentTimeMillis));
  15. return first.orElse(null);
  16. }

为了模拟一个耗时,我在请求处理的时候对当前处理线程sleep500ms。然后使用对应postman进行请求调用,调用后查看控制台打印:

  1. 处理耗时:513 ms

效果还行,但是现在需要对所有的调用都进行日志记录呢?有的同学可能会说,直接开写,一个一个加。牛!此外,我今天的需求是耗时打印,我以后可能需要耗时上传进行预警了,再后来我还希望统计各个API的调用接口,似乎我们愈来愈无法控制这些需求了。每变更一个需求,都需要我们去对每个API进行修改。

还好,我们还有一大杀器:AOP。

AOP

AOP全称Aspect Oriented Programming意为面向切面编程,也叫做面向方法编程,是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统一添加功能的技术。

  1. 流程起点
  2. |
  3. |
  4. ---> 切入点1
  5. |
  6. |
  7. ---> 切入点2
  8. |
  9. V
  10. 流程终点

上图的流程中,我们可以在任何希望的时候切入处理。其好处的就是是的业务逻辑各个部分之间的耦合度降低,提高程序的可重用性。我们可以在完全不侵入业务逻辑代码的情况下就完成各个阶段的切入处理。

核心术语

连接点(JoinPoint)

连接点是在应用执行过程中能够插入切面(Aspect)的一个点。这些点可以是调用方法时、甚至修改一个字段时。它是一个虚拟的概念,例如坐地铁的时候,每一个站都可以下车,那么这每一个站都是一个连接点。假如一个对象中有多个方法,那么这个每一个方法就是一个连接点。

切入点(Pointcut)

切入点是一些特殊的连接点,是具体附加通知的地方。例如坐地铁的时候,具体在某个站下车,那这个站就是切入点。

通知(Advice)

在某个特定的Pointcut切点上需要的执行的动作,如日志记录,权限校验等具体要应用切入点的代码。

五种通知类型:

  • 环绕通知:@Around
  • 前置通知:@Before
  • 返回通知:@After
  • 正常返回通知:@AfterReturning
  • 异常返回通知:@AfterThrowing

切面(Aspect)

切面是通知切入点的结合,通知规定了在什么时机干什么事,切入点规定了在什么地方。如“在8点钟在天府广场站下车“ 就是一个切面:时间8点,动作下车就是一个通知;西站就是一个切入点。

对于概念术语还是很抽象,我们直接编写一个切面吧。编写切面之前,首先需要引入相关的依赖。因为切面相关的模块是可选模块,我们在pom中添加如下的依赖:

  1. <dependencies>
  2. <!-- 其他依赖 -->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-aop</artifactId>
  6. </dependency>
  7. </dependencies>

完成依赖导入后,我们编写一个切面类:

  1. /**
  2. * 定义一个日志切面
  3. */
  4. @Aspect
  5. @Component // 切面不是Bean,需要添加注解
  6. public class LogAspect {
  7. private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
  8. /**
  9. * 定义切点表明要通知的地方
  10. * 这里使用了pointcut expression表达式,具体语法请自行搜索
  11. * 这里解释为包 com.compilemind.guide包以及子包的所有类的所有公共方法
  12. */
  13. @Pointcut("execution(* com.compilemind.guide..*.*(..))")
  14. public void webLog() {
  15. }
  16. /**
  17. * 指代上面的切点:webLog,并且是调用前执行
  18. */
  19. @Before("webLog()")
  20. public void doBefore(JoinPoint joinPoint) {
  21. ServletRequestAttributes requestAttributes =
  22. (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  23. if (requestAttributes == null) {
  24. return;
  25. }
  26. HttpServletRequest request = requestAttributes.getRequest();
  27. LOGGER.info("发生请求:" + request.getRequestURI());
  28. LOGGER.info("调用方法:" + joinPoint.getSignature());
  29. }
  30. /**
  31. * 一个关于对应切点的环绕执行的处理
  32. */
  33. @Around("webLog()")
  34. public Object around(ProceedingJoinPoint joinPoint) {
  35. long currentTimeMillis = System.currentTimeMillis();
  36. // 调用参数
  37. Object[] invokeArgs = joinPoint.getArgs();
  38. // 返回数据
  39. Object returnObj;
  40. try {
  41. // 执行对应的方法,得到结果
  42. returnObj = joinPoint.proceed(invokeArgs);
  43. } catch (Throwable e) {
  44. LOGGER.error("统计某方法执行耗时环绕通知出错", e);
  45. return null;
  46. }
  47. LOGGER.info("处理耗时:{} ms", System.currentTimeMillis() - currentTimeMillis);
  48. return returnObj;
  49. }
  50. }

这个切面由以下几个部分组成:

  1. 在类上使用@Aspect注解标记为切面,使用@Component注解标记为组件,由Spring管理;
  2. 编写方法webLog,并在其方法上添加注解@Pointcut,并按照规则填写切点的位置;
  3. 分别编写由@Before@Around注解标记的方法,用以处理对应的切点位置处理前整个环绕的处理代码。

最后,让我们再次启动程序,进行相关的API调用,可以看到输出:

  1. 2021-08-09 16:42:46.494 INFO 18528 --- [nio-8080-exec-2] c.c.guide.chapter04_05.aspect.LogAspect : 发生请求:/books/1
  2. 2021-08-09 16:42:46.495 INFO 18528 --- [nio-8080-exec-2] c.c.guide.chapter04_05.aspect.LogAspect : 调用方法:Book com.compilemind.guide.chapter04_05.controller.BookController.getBookById(String)
  3. 处理耗时:502 ms
  4. 2021-08-09 16:42:47.004 INFO 18528 --- [nio-8080-exec-2] c.c.guide.chapter04_05.aspect.LogAspect : 处理耗时:510 ms

极简SpringBoot指南-Chapter05-SpringBoot中的AOP面向切面编程简介的更多相关文章

  1. AOP 面向切面编程, Attribute在项目中的应用

    一.AOP(面向切面编程)简介 在我们平时的开发中,我们一般都是面对对象编程,面向对象的特点是继承.多态和封装,我们的业务逻辑代码主要是写在这一个个的类中,但我们在实现业务的同时,难免也到多个重复的操 ...

  2. 基于SpringBoot AOP面向切面编程实现Redis分布式锁

    基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式 ...

  3. 快速高效掌握企业级项目中的Spring面向切面编程应用,外带讲面试技巧

    Spring面向切面编程(AOP)是企业级应用的基石,可以这样说,如果大家要升级到高级程序员,这部分的知识必不可少. 这里我们将结合一些具体的案例来讲述这部分的知识,并且还将给出AOP部分的一些常见面 ...

  4. 浅谈Java中的AOP面向切面的变成和控制反转IOC

    https://blog.csdn.net/hi_kevin/article/details/7325554 https://www.cnblogs.com/zedosu/p/6632260.html ...

  5. 【spring-boot】spring aop 面向切面编程初接触--切点表达式

    众所周知,spring最核心的两个功能是aop和ioc,即面向切面,控制反转.这里我们探讨一下如何使用spring aop. 1.何为aop aop全称Aspect Oriented Programm ...

  6. 【spring-boot】spring aop 面向切面编程初接触

    众所周知,spring最核心的两个功能是aop和ioc,即面向切面,控制反转.这里我们探讨一下如何使用spring aop. 1.何为aop aop全称Aspect Oriented Programm ...

  7. 在.NET项目中使用PostSharp,实现AOP面向切面编程处理

    PostSharp是一种Aspect Oriented Programming 面向切面(或面向方面)的组件框架,适用在.NET开发中,本篇主要介绍Postsharp在.NET开发中的相关知识,以及一 ...

  8. 利用多态,实现一般处理程序(ashx)中的AOP(切面编程)

    本文是对工作中的项目进行代码优化(完善登陆验证的AOP切面编程)时,所遇到的各种解决方案思考过程. 项目背景:由ashx+nvelocity构建的简单B/S问卷系统,现需要优化登录验证环节(时隔若干个 ...

  9. AOP面向切面编程在Android中的使用

    GitHub地址(欢迎下载完整Demo) https://github.com/ganchuanpu/AOPDemo 项目需求描述 我想类似于这样的个人中心的界面,大家都不会陌生吧.那几个有箭头的地方 ...

随机推荐

  1. Java HdAcm1174

    空间一般直线的方程是:(x-x0)/a=(y-y0)/b=(z-z0)/c,这是一条过(x0,y0,z0),方向矢量为{a,b,c}的直线.假设已知点的坐标是A(e,f,g),过A点,且与{a,b,c ...

  2. docker学习之network:初识网络配置

    起因 我的开发环境需要一个python代码运行环境.reids服务和mysql服务. 由于以前,我的开发环境是mac,而CI和线上运行环境是centos,偶尔会出项本地单元测试跑不过,而CI可以过.这 ...

  3. 给MediaWiki增加看板娘

    我们想给我们的mediawiki增加个像我博客里这样的看板娘,那么怎么做才好呢? 其实很简单,只要在相应的模板文件里增加指定代码就好了! 修改模板文件 找到模板文件skins/Vector/Vecto ...

  4. 求方程 p+q+r+s+t=pqrst 的全体自然数解(约定p<=q<=r<=s<=t)

    解:方程左右的表达式分别记为u和v. 由题设有5t>=u. 0本来是不算入自然数的,现在的趋势是把0也算作自然数. 若p=0,则v=0,为使得u=0成立,q.r.s.t都必需为0. 这样就得到方 ...

  5. 【Azure 应用服务】App Service For Windows 环境中部署Python站点后,如何继续访问静态资源文件呢(Serving Static Files)?

    问题描述 当创建一个App Service 后,运行时环境和版本选择Windows 和 Python 3.6. 登录Kudu 站点查看,默认的文件有 web.config, hostingstart- ...

  6. MySQL-存储引擎-Myisam

    mysql> create table myisam_char(name char(10)) engine=myisam; Query OK, 0 rows affected (0.01 sec ...

  7. CodeReview杂谈

    豆皮粉儿们,大家好,又见面啦,今天由字节跳动的"躬冯"带来一个 code review 的故事. 作者:躬冯 2020年元旦假期到来的时候,孙总攒了个局,又把当年一起创造过屎山的咱 ...

  8. Appium自动化(11) - 详解 Applications 类里的方法和源码解析

    如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 Applications 类 ...

  9. 转:C#根据条件设置datagridview行的颜色

    1 private void LoadData() 2 { 3 DataTable tblDatas = new DataTable(); 4 tblDatas.Columns.Add("I ...

  10. jQuery-01

    day01 - jQuery 学习目标: 能够说出什么是 jQuery 能够说出 jQuery 的优点 能够简单使用 jQuery 能够说出 DOM 对象和 jQuery 对象的区别 能够写出常用的 ...