【Spring Boot】整合 AOP
- 认识AOP
1.1 什么是AOP
1.2 AOP中的概念 - SpringBoot整合AOP代码示例
2.1 使用execution(路径表达式)
2.2 使用annotation(注解) - JoinPoint 对象
- ProceedingJoinPoint对象
- 使用了环绕通知后,全局异常捕获失效的解决办法
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相关推荐
- druid监控页面_Spring boot学习(四)Spring boot整合Druid
前言 在上一篇博客中我们介绍了Spring boot配置Mybatis,但是并没有配置连接池,这在实际开发过程中肯定是不切实际的,多次的数据库连接会给程序和数据库都带来没必要的负担,这一篇博客我将介绍 ...
- spring boot整合spring security笔记
最近自己做了一个小项目,正在进行springboot和spring Security的整合,有一丢丢的感悟,在这里分享一下: 首先,spring boot整合spring security最好是使用T ...
- RabbitMQ使用及与spring boot整合
1.MQ 消息队列(Message Queue,简称MQ)--应用程序和应用程序之间的通信方法 应用:不同进程Process/线程Thread之间通信 比较流行的中间件: ActiveMQ Rabbi ...
- Spring Boot 教程(三): Spring Boot 整合Mybatis
教程简介 本项目内容为Spring Boot教程样例.目的是通过学习本系列教程,读者可以从0到1掌握spring boot的知识,并且可以运用到项目中.如您觉得该项目对您有用,欢迎点击收藏和点赞按钮, ...
- 五、spring boot整合mybatis-plus
spring boot整合mybatis-plus 简介 mybatis 增强工具包,简化 CRUD 操作. 文档 http://mp.baomidou.com http://mybatis.plus ...
- spring boot 整合mybatis 无法输出sql的问题
使用spring boot整合mybatis,测试功能的时候,遇到到了sql问题,想要从日志上看哪里错了,但是怎么都无法输出执行的sql,我使用的是log4j2,百度了一下,很多博客都说,加上下面的日 ...
- Spring boot 整合 Mybatis 实现增删改查(MyEclipse版)
1.首先搭建好一个Spring boot 程序,编写好启动类. 启动类代码如下: @SpringBootApplication public class Start {public static vo ...
- spring boot 系列之四:spring boot 整合JPA
上一篇我们讲了spring boot 整合JdbcTemplate来进行数据的持久化, 这篇我们来说下怎么通过spring boot 整合JPA来实现数据的持久化. 一.代码实现 修改pom,引入依赖 ...
- freemarker ftl模板_Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker
今天来聊聊 Spring Boot 整合 Freemarker. Freemarker 简介 这是一个相当老牌的开源的免费的模版引擎.通过 Freemarker 模版,我们可以将数据渲染成 HTML ...
- springboot整合hibernate_峰哥说技术系列-17 .Spring Boot 整合 Spring Data JPA
今日份主题 Spring Boot 整合 Spring Data JPA JPA(Java Persistence API)是用于对象持久化的 API,是Java EE 5.0 平台标准的 ORM 规 ...
最新文章
- 软件工程心理学之---让客户知错,但不能向你发怒
- 多波次导弹发射中的规划问题(二) 问题一解答
- [转载]ASP.NET Core 源码阅读笔记(1) ---Microsoft.Extensions.DependencyInjection
- Nginx-07:Nginx配置实例之动静分离
- BZOJ2054: 疯狂的馒头(并查集)
- C# 读取EXCEL文件的三种经典方法
- wolfssl 何如 https post_干货:手把手教你优化关键词|亚马逊|流量|搜索量|长尾词|https...
- 克服VR眩晕之帧数:提升UE4内容实时渲染效率
- 第十章:SpringCloud Zuul路由器和过滤器
- [前端网站毕业设计源码]基于html的大学校园官网(jQuery)(静态网页)
- 高等数学(第七版)同济大学 习题3-1 个人解答
- oracle怎么启动oem,Oracle 启动OEM
- Groovy语言 Grails框架入门
- 【网页制作】CSS文本和字体属性讲解【附讲解视频】
- andriod TV 获取已连接蓝牙遥控器电池电量总结
- biu爱心html,biu爱心给你表情包 - biu爱心给你微信表情包 - biu爱心给你QQ表情包 - 发表情 fabiaoqing.com...
- Camera:高斯模糊
- NLP基础——python的jieba用于词类分割用法总结(1)
- tcp_keepalive的设置
- 标准DH建模与改进DH建模(一)——标准DH建模方法整理与总结