1. 认识AOP
    1.1 什么是AOP
    1.2 AOP中的概念
  2. SpringBoot整合AOP代码示例
    2.1 使用execution(路径表达式)
    2.2 使用annotation(注解)
  3. JoinPoint 对象
  4. ProceedingJoinPoint对象
  5. 使用了环绕通知后,全局异常捕获失效的解决办法

1 认识Spring AOP

1.1 什么是AOP

AOP (Aspect Oiented Programn,面向切面编程)把业务功能分为核心、非核心两部分。

● 核心业务功能:用户登录、增加数据、删除数据。
● 非核心业务功能:性能统计、日志、事务管理。

在Spring的面向切面编程( AOP )思想里,非核心业务功能被定义为切面。核心业务功能和切面功能先被分别进行独立开发,然后把切面功能和核心业务功能“编织”在一起,这就是AOP。

未使用AOP的程序如 图1 所示,使用AOP的程序如 图2 所示。由此可见,AOP将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以便减少系统的重复代码,降低模块间的耦合度,利于未来的拓展和维护。这正是AOP的目的,它是Spring最为重要的功能之一,被广 泛使用。

1.2 AOP中的概念

● 切入点(pointcut):在哪些类、哪些方法上切入。

● 通知(advice):在方法前、方法后、方法前后做什么。

● 切面 = 切入点 + 通知。即在什么时机、什么地方、做什么。

● 织入(weaving):把切面加入对象,并创建出代理对象的过程

● 环绕通知(around):AOP中最强大、灵活的通知,它集成了前置和后置通知,保留了连接点原有的方法。

AOP的体系可以梳理为下图:

2 AOP代码示例

首先导入AOP的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.1 使用execution(路径表达式)

@Slf4j
@Aspect
@Component
public class LogAspect {ThreadLocal<Long> startTime = new ThreadLocal<>();/*** execution函数用于匹配方法执行的连接点,语法为:* execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选))* 参数部分允许使用通配符:* *  匹配任意字符,但只能匹配一个元素* .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用* +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类*/@Pointcut("execution(public * work.pcdd.aop_demo.controller.*.*(..))")private void webLog() {}/*** 前置通知:在目标方法被调用之前调用通知功能*/@Before("webLog()")public void doBefore(JoinPoint jp) {System.out.println("=====================doBefore======================");// 接收到请求ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录请求内容log.info("URL : {}", request.getRequestURL());log.info("HTTP方法 : {}", request.getMethod());log.info("IP地址 : {}", request.getRemoteAddr());log.info("类的方法 : {}.{}", jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName());log.info("方法参数 : {}", Arrays.toString(jp.getArgs()));System.out.println("=====================doBefore======================");}/*** 返回通知:在目标方法成功执行之后调用通知*/@AfterReturning(pointcut = "webLog()", returning = "result")public void doAfterReturning(Object result) {System.out.println("=====================doAfterReturning======================");// 处理完请求,返回内容System.out.println("方法的返回值 : " + result);System.out.println("=====================doAfterReturning======================");}/*** 最终通知:在目标方法完成之后调用通知,不管是抛出异常或者正常退出都会执行*/@After("webLog()")public void doAfter(JoinPoint jp) {System.out.println("=====================doAfter======================");System.out.println("方法最后执行.....");System.out.println("=====================doAfter======================");}/*** 环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行,相当于MethodInterceptor*/@Around("webLog()")public Object doAround(ProceedingJoinPoint pjp) {System.out.println("=====================doAround======================");System.out.println("方法环绕start.....");startTime.set(System.currentTimeMillis());try {Object o = pjp.proceed();System.out.println("方法环绕proceed,结果是 :" + o);System.out.println("方法执行耗时:" + (System.currentTimeMillis() - startTime.get()) + " ms");System.out.println("=====================doAround======================");return o;} catch (Throwable e) {e.printStackTrace();return null;}}/*** 异常通知:在目标方法抛出异常后调用通知*/@AfterThrowing(pointcut = "webLog()", throwing = "ex")public void doThrows(JoinPoint jp, Exception ex) {System.out.println("=====================doThrows======================");System.out.println("方法异常时执行\n发生的异常:" + ex.getClass().getName() + "\n异常信息:" + ex.getMessage());System.out.println("=====================doThrows======================");}}

controller代码如下,返回当前日期时间

@RestController
public class BaseController {@GetMapping("/api1")public Map<String, Object> api1() {Map<String, Object> map = new HashMap<>(16);map.put("nowTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));return map;}
}

调用接口,控制台输出结果如下:

=====================doAround======================
方法环绕start.....
=====================doBefore======================
2021-05-09 23:20:58.013  INFO 14772 --- [nio-8080-exec-1] work.pcdd.aop_demo.aop.LogAspect         : URL : http://192.168.85.1:8080/api1
2021-05-09 23:20:58.014  INFO 14772 --- [nio-8080-exec-1] work.pcdd.aop_demo.aop.LogAspect         : HTTP方法 : GET
2021-05-09 23:20:58.014  INFO 14772 --- [nio-8080-exec-1] work.pcdd.aop_demo.aop.LogAspect         : IP地址 : 192.168.85.1
2021-05-09 23:20:58.015  INFO 14772 --- [nio-8080-exec-1] work.pcdd.aop_demo.aop.LogAspect         : 类的方法 : work.pcdd.aop_demo.controller.BaseController.api1
2021-05-09 23:20:58.016  INFO 14772 --- [nio-8080-exec-1] work.pcdd.aop_demo.aop.LogAspect         : 方法参数 : []
=====================doBefore======================
=====================doAfterReturning======================
方法的返回值 : {nowTime=2021-05-09 23:20:58}
=====================doAfterReturning======================
=====================doAfter======================
方法最后执行.....
=====================doAfter======================
方法环绕proceed,结果是 :{nowTime=2021-05-09 23:20:58}
方法执行耗时:18 ms
=====================doAround======================

代码解释如下:

● @Aspect:标记为切面类

● @Component:把切面类加入IoC容器中,让Spring进行管理

● @Before:再切入点开始处切入内容。

● @After:在切入点结尾处切入内容

● @AfterReturning:在切入点返回内容之后切入内容,可以用来对处理返回值做一些加工处理。

● @Around:在切入点前后切入内容,并控制何时执行切入点自身的内容。

● @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。

注:
● 被@Around标注的方法,必须要有一个ProceedingJoinPoint类型的参数,其他的可以不加参数
● @Order用于指定Spring IOC容器中Bean的执行顺序的优先级(不是定义Bean的加载顺序),值越小拥有越高的优先级,可为负数。

源码如下:

2.1 使用annotation(注解)

首先定义一个注解(不想自定义注解使用系统注解也可以,比如@GetMapping)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {String value() default "";
}

定义切面

@Slf4j
@Aspect
@Component
public class AnnotationAspect {ThreadLocal<Long> startTime = new ThreadLocal<>();@Pointcut("@annotation(work.pcdd.aop_demo.annotation.MyAnnotation))")private void myAnnotationCheck() {}@Before("myAnnotationCheck()")public void doBefore(JoinPoint jp) {System.out.println("=====================doBefore======================");startTime.set(System.currentTimeMillis());ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();log.info("URL : {}", request.getRequestURL());log.info("HTTP方法 : {}", request.getMethod());log.info("IP地址 : {}", request.getRemoteAddr());log.info("类的方法 : {}.{}", jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName());log.info("方法参数 : {}", Arrays.toString(jp.getArgs()));System.out.println("=====================doBefore======================");}/*** 后置增强*/@AfterReturning(pointcut = "myAnnotationCheck()", returning = "result")public void doAfterReturning(Object result) {System.out.println("=====================doAfterReturning======================");log.info("方法的返回值 : {}", result);log.info("耗时 : {}ms", (System.currentTimeMillis() - startTime.get()));System.out.println("=====================doAfterReturning======================");}
}

controller代码如下,先阻塞两秒,观察耗时

@RestController
public class BaseController {@MyAnnotation@GetMapping("/api2")public String api2() throws InterruptedException {TimeUnit.SECONDS.sleep(2);return "api2 调用成功";}
}

执行结果如下:

=====================doBefore======================
2021-05-09 23:43:47.144  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : URL : http://192.168.85.1:8080/api2
2021-05-09 23:43:47.144  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : HTTP方法 : GET
2021-05-09 23:43:47.144  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : IP地址 : 192.168.85.1
2021-05-09 23:43:47.145  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : 类的方法 : work.pcdd.aop_demo.controller.BaseController.api2
2021-05-09 23:43:47.145  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : 方法参数 : []
=====================doBefore======================
=====================doAfterReturning======================
2021-05-09 23:43:49.152  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : 方法的返回值 : api2 调用成功
2021-05-09 23:43:49.152  INFO 14772 --- [nio-8080-exec-3] work.pcdd.aop_demo.aop.AnnotationAspect  : 耗时 : 2008ms
=====================doAfterReturning======================

获取注解属性的方法:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
myAnnotationCheck annotation=signature.getMethod().getDeclaredAnnotation(myAnnotationCheck.class);
// 获取value属性
String value = annotation.value();

3 JoinPoint 对象

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.。

方法名 功能
Signature getSignature() 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs() 获取传入目标方法的参数对象
Object getTarget() 获取被代理的对象
Object getThis() 获取代理对象

4 ProceedingJoinPoint 对象

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中

方法名 功能
Object proceed() throws Throwable 执行目标方法
Object proceed(Object[] var1) throws Throwable 传入的新的参数去执行目标方法

5 使用了环绕通知后,全局异常捕获失效的解决办法

观察环绕通知代码,发现异常被环绕通知给捕获了

    @Around("apiLog()")public void logAround(ProceedingJoinPoint pjp) {System.out.println("=====================doAround======================");try {// 将控制权交给被通知的方法pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("=====================doAround======================");}

解决方法就是抛出这个异常,具体操作就是给方法加上throws Throwable

 @Around("apiLog()")public void logAround(ProceedingJoinPoint pjp) throws Throwable {System.out.println("=====================doAround======================");// 将控制权交给被通知的方法pjp.proceed();System.out.println("=====================doAround======================");}

完整代码已上传至gitee:https://gitee.com/pcd09/springboot-aop-demo

部分参考:
https://blog.csdn.net/qq_15037231/article/details/80624064
https://blog.csdn.net/mu_wind/article/details/102758005

【Spring Boot】整合 AOP相关推荐

  1. druid监控页面_Spring boot学习(四)Spring boot整合Druid

    前言 在上一篇博客中我们介绍了Spring boot配置Mybatis,但是并没有配置连接池,这在实际开发过程中肯定是不切实际的,多次的数据库连接会给程序和数据库都带来没必要的负担,这一篇博客我将介绍 ...

  2. spring boot整合spring security笔记

    最近自己做了一个小项目,正在进行springboot和spring Security的整合,有一丢丢的感悟,在这里分享一下: 首先,spring boot整合spring security最好是使用T ...

  3. RabbitMQ使用及与spring boot整合

    1.MQ 消息队列(Message Queue,简称MQ)--应用程序和应用程序之间的通信方法 应用:不同进程Process/线程Thread之间通信 比较流行的中间件: ActiveMQ Rabbi ...

  4. Spring Boot 教程(三): Spring Boot 整合Mybatis

    教程简介 本项目内容为Spring Boot教程样例.目的是通过学习本系列教程,读者可以从0到1掌握spring boot的知识,并且可以运用到项目中.如您觉得该项目对您有用,欢迎点击收藏和点赞按钮, ...

  5. 五、spring boot整合mybatis-plus

    spring boot整合mybatis-plus 简介 mybatis 增强工具包,简化 CRUD 操作. 文档 http://mp.baomidou.com http://mybatis.plus ...

  6. spring boot 整合mybatis 无法输出sql的问题

    使用spring boot整合mybatis,测试功能的时候,遇到到了sql问题,想要从日志上看哪里错了,但是怎么都无法输出执行的sql,我使用的是log4j2,百度了一下,很多博客都说,加上下面的日 ...

  7. Spring boot 整合 Mybatis 实现增删改查(MyEclipse版)

    1.首先搭建好一个Spring boot 程序,编写好启动类. 启动类代码如下: @SpringBootApplication public class Start {public static vo ...

  8. spring boot 系列之四:spring boot 整合JPA

    上一篇我们讲了spring boot 整合JdbcTemplate来进行数据的持久化, 这篇我们来说下怎么通过spring boot 整合JPA来实现数据的持久化. 一.代码实现 修改pom,引入依赖 ...

  9. freemarker ftl模板_Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker

    今天来聊聊 Spring Boot 整合 Freemarker. Freemarker 简介 这是一个相当老牌的开源的免费的模版引擎.通过 Freemarker 模版,我们可以将数据渲染成 HTML ...

  10. springboot整合hibernate_峰哥说技术系列-17 .Spring Boot 整合 Spring Data JPA

    今日份主题 Spring Boot 整合 Spring Data JPA JPA(Java Persistence API)是用于对象持久化的 API,是Java EE 5.0 平台标准的 ORM 规 ...

最新文章

  1. 软件工程心理学之---让客户知错,但不能向你发怒
  2. 多波次导弹发射中的规划问题(二) 问题一解答
  3. [转载]ASP.NET Core 源码阅读笔记(1) ---Microsoft.Extensions.DependencyInjection
  4. Nginx-07:Nginx配置实例之动静分离
  5. BZOJ2054: 疯狂的馒头(并查集)
  6. C# 读取EXCEL文件的三种经典方法
  7. wolfssl 何如 https post_干货:手把手教你优化关键词|亚马逊|流量|搜索量|长尾词|https...
  8. 克服VR眩晕之帧数:提升UE4内容实时渲染效率
  9. 第十章:SpringCloud Zuul路由器和过滤器
  10. [前端网站毕业设计源码]基于html的大学校园官网(jQuery)(静态网页)
  11. 高等数学(第七版)同济大学 习题3-1 个人解答
  12. oracle怎么启动oem,Oracle 启动OEM
  13. Groovy语言 Grails框架入门
  14. 【网页制作】CSS文本和字体属性讲解【附讲解视频】
  15. andriod TV 获取已连接蓝牙遥控器电池电量总结
  16. biu爱心html,biu爱心给你表情包 - biu爱心给你微信表情包 - biu爱心给你QQ表情包 - 发表情 fabiaoqing.com...
  17. Camera:高斯模糊
  18. NLP基础——python的jieba用于词类分割用法总结(1)
  19. tcp_keepalive的设置
  20. 标准DH建模与改进DH建模(一)——标准DH建模方法整理与总结

热门文章

  1. exchange设置收发邮件大小
  2. 怎么用MathType给公式加三角着重号
  3. 证考网:考过二级建造师后必须要注册吗?
  4. PIC单片机ISP下载外围链接电路
  5. java 奇偶数据排序算法,简单讲解奇偶排序算法及在Java数组中的实现
  6. 抖音号运营爆量爆单技巧
  7. 通达信最新 行情服务器,联合证券通达信行情_通达信高级行情服务器_通达信行情接口...
  8. MySQL从安装到精通(单表)
  9. 爱心慈善跑活动暖心开幕 中国移动携手谷爱凌启航2022爱心行动
  10. python将多图绘制在同一画布中