Spring Boot————AOP入门案例及切面优先级设置
看了这篇文章,如果你还是不会用AOP来写程序,请你打我!! =.=|||
引言
Spring AOP是一个对AOP原理的一种实现方式,另外还有其他的AOP实现如AspectJ等。
AOP意为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP面向对象编程的一种补足。它是软件开发中的一个热点技术,Spring AOP 也是Spring框架的核心特性之一(另一个核心特性是IOC)。
通过AOP技术,我们希望实现一种通用逻辑的解耦,解决一些系统层面上的问题,如日志、事务、权限等,从而提高应用的可重用性和可维护性,和开发效率。
Struts2的拦截器设计就是基于AOP的思想,是非常经典的理论实践案例。
重要概念
AOP中包括 5 大核心概念:切面(Aspect)、连接点(JoinPoint)、通知(Advice)、切入点(Pointcut)、AOP代理(Proxy)。(记忆口诀:通知 代理 厨师两点(连接点、切入点)切面包。)
关于前面四点,将会直接涉及到相关编码的实现方式,因此将会结合代码进行解释,在这里简单阐述一下AOP代理。
AOP代理,是AOP框架如Spring AOP创建的对象,代理就是对目标对象进行增强,Spring AOP中的代理默认使用JDK动态代理,同时支持CGLIB代理,前者基于接口,后者基于子类。在Spring AOP中,其功能依然离不开IOC容器,代理的生成、管理以及其依赖关系都是由IOC容器负责,而根据目前的开发提倡“面向接口编程”,因此大多使用JDK动态代理。
五大通知类型
1、前置通知 [ Before advice ] :在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常;
2、正常返回通知 [ After returning advice ] :在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行;
3、异常返回通知 [ After throwing advice ] :在连接点抛出异常后执行;
4、返回通知 [ After (finally) advice ] :在连接点执行完成后执行,不管正常执行完成,还是抛出异常,都会执行返回通知中的内容;
5、环绕通知 [ Around advice ] :环绕通知围绕在连接点前后,比如一个方法调用的前后。这种通知是最强大的通知,能在方法调用前后自定义一些操作。
应用案例分析
在OOP中的基本单元是类,而在AOP中的基本单元是Aspect,它实际上也是一个类,只不过这个类用于管理一些具体的通知方法和切入点。
所谓的连接点,实际上就是一个具体的业务方法,比如Controller中的一个请求方法,而切入点则是带有通知的连接点,在程序中主要体现为书写切入点表达式,这个表达式将会定义一个连接点。
就以Controller中的一个请求方法为例,通过AOP的方式实现一定的业务逻辑。
这个逻辑是:GET请求某一方法,然后通过一个Aspect来实现在这个方法调用前和调用后做一些日志输出处理。
引入依赖jar包
基于spring boot 的maven依赖如下,如果是仅使用spring框架的话,请参考其他资料:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义业务方法(连接点)
这个方法就是后面AOP切面中的那个连接点,方法非常简单,仅仅接收一个姓名和性别,并输出 “某某做作业......” :
@RestController
public class DoHomeWorkController {@GetMapping("/dohomework")public void doHomeWork(String name, Gender gender) {System.out.println(name + "做作业... ...");}
}
定义切面类、定义切入点及通知方法
下面的代码中,@Aspect、@Pointcut、@Component都是必须的(@Component用于将这个切面类注入到 IOC容器中,如果不用@Component就用@Bean的方式也是可以的,但总之切面类必须被注入到 IOC容器中,这也就是前面说的Spring AOP不能脱离IOC容器的体现)。而@Before用来定义一个前面提到过的五大通知类型中的 Before advice类型的通知方法,这个根据具体的需要可以进行选择。
@Pointcut注解的参数是一个表达式,可以当做是一个固定的写法,“ * ” 表示任意返回值,“ .. ” 也是一种通配。当然,方法的全名可以使用编辑器的复制功能,具体关于execution表达式的说明,在此不做展开讨论。
@Aspect
@Component
public class DoHomeWorkAspect {/** 定义切入点 */@Pointcut("execution(* com.example.demo.controller.DoHomeWorkController.doHomeWork(..))")public void homeWorkPointcut() {}/** 定义Before advice通知类型处理方法 */@Before("homeWorkPointcut()")public void beforeHomeWork() {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();System.out.println(request.getParameter("name") + "想先吃个冰淇淋......");}
}
再简单说一下通过RequestContextHolder这个最终获取request的操作,就当是一个固定写法,可以从请求上下文中拿到当前的请求对象,并从请求中获得一些信息,更详细的API用法不做展开。
执行结果
启动项目,浏览器地址栏输入:
控制台显示如下:
可以从输出结果中看到,在执行doHomeWork(String name, Gender gender) 方法之前先执行了切面类中定义的beforeHomeWork()方法,成功的完成了在切入点之前执行一个操作的需要。这就是Spring AOP的典型应用。
环绕通知实现
在上一节“应用案例分析”中介绍了Before advice的使用方式,而Spring AOP的通知类型有五种,在Spring 框架里分别有对应的注解来代表每一种通知类型,它们分别是:
@Before 对应——>前置通知 [ Before advice ]
@AfterReturning 对应——>正常返回通知 [ After returning advice ]
@AfterThrowing 对应——>异常返回通知 [ After throwing advice ]
@After 对应——>返回通知 [ After (finally) advice ]
@Around 对应——>环绕通知 [ Around advice ]
其中,前四种通知类型,与@Before的使用完全相同,根据各自不同的使用定义自行选择。
需要说明的是@Around的使用。在定义环绕通知方法的时候,需要传入一个org.aspectj.lang.ProceedingJoinPoint 对象:
@Around("homeWorkPointcut()")public void around(ProceedingJoinPoint joinPoint) {System.out.println("环绕通知,方法执行前");try {joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("环绕通知,方法执行后");}
执行结果如下:
根据输出结果,我们注意到了一个问题,即@Around先于@Before通知执行。这就引出了一个非常重要的问题,即各类型通知执行的先后顺序。
各类型通知执行先后顺序
在实际开发中,有时候我们会针对同一个切入点进行多种Aspect包装,比如,可以有一个Aspect管理对一个方法进行日志打印的通知,而另一个Aspect管理对这个方法的一些校验工作。因此,涉及到两类问题:
1、同一个切入点不同通知的执行顺序
2、同一个切入点不同切面的执行顺序
我们在前面的“环绕通知实现”结果中看到,@Around是先于@Before执行的,这就是其中一个问题的引出,即同一个切入点不同通知的执行顺序。来看下面这张图:
可以看到Aspect1 和Aspect2两个切面类中所有通知类型的执行顺序,Method是具体的切入点,order代表优先级,它根据一个int值来判断优先级的高低,数字越小,优先级越高!所以,不同的切面,实际上是环绕于切入点的同心圆:
@Order注解改变优先级
@order注解可以使用在类或方法上,但是,直接作用于方法上是无法奏效的,目前的使用方法都是通过标记在切面类上,来实现两个切面的优先级。
@Order注解接收一个int类型的参数,这个参数可以是任意整型数值,数值小的,优先级高。
对于使用@Order来改变通知方法执行的优先级,亲测无法生效。也就是说就算你使用@Order注解,让@Before的优先级高于@Around也依然不会得到想要的结果,而且,如果在一个Aspect类中有两个@Before,并使用@Order来分配这两个@Before的优先级依然不会生效。
因此,在实际开发的过程中,应该避免在一个Aspect类中有多个相同的通知类型,否则,就算使用@Order来区分优先级,可能最后的效果也不符预期。
那么,关于@Order注解实现优先级的方式,我个人总结了以下几条经验:
1、在一个Aspect类中不要有多个同种类型的通知,如多个@Before、多个@After;
2、不要在通知方法上使用@Order来区分优先级,要遵循默认的通知方法优先级(同心圆模型);
3、如果避免不了有相同类型的通知,要区分在不同的Aspect类中,并且通过@Order(1)、@Order(2)、@Order(3)... 来区分Aspect类的优先级,即以切面类作为优先级的区分单元,而不是通知方法;
4、在编写多个通知方法时,应当把实际业务需要与默认通知优先级(同心圆模型)结合编码。
综上,就是关于AOP的实践与总结,总的来说,还是收获颇丰的,其中针对于@Order的解释,是个人的分析和经验总结,因为把@Order用在通知方法上真的不好使,而且也并未找到比较好的解决办法,所以还是应该通过巧妙的方式避开这个坑。如有任何疑问,欢迎各位看官文末留言。:)
鸣谢
《Spring AOP APIs》
《AOP-百度百科》
《浅谈spring aop的五种通知类型》
《Spring AOP详细介绍》
《Spring AOP之坑:完全搞清楚advice的执行顺序》
Spring Boot————AOP入门案例及切面优先级设置相关推荐
- Spring Boot从入门到精通(超详细)
Spring Boot从入门到精通(超详细) _kayden_ 2020-07-20 15:19:22 9491 正在上传-重新上传取消 收藏 184 分类专栏: springboot 文章标签: ...
- Spring Boot 高效入门实战
凭借开箱即用,远离繁琐的配置等特性,Spring Boot 已经成为 Java 开发者人人必学必会的开源项目.那么开发者该如何快速上手Spring Boot 呢? 进入Spring Boot世界 Ja ...
- Spring Boot AOP处理方法的入参和返回值
前言 IOC和AOP是Spring 中最重要的两个模块.这里练习一下如何使用Spring Boot AOP处理方法的入参和返回值. Spring AOP的简单介绍: AOP(Aspect-Orient ...
- spring boot 快速入门
文章来源:https://www.cnblogs.com/junyang/p/8151802.html spring boot入门 -- 介绍和第一个例子 "越来越多的企业选择使用sprin ...
- 【720开发】 spring boot 快速入门
spring boot 快速入门 通过构建简单的REST应用,了解spring boot的开发基本流程,验证其简单.易用特性. 环境要求 Spring Boot 2.0.0.BUILD-SNAPSHO ...
- 视频教程-Spring Boot实战入门视频课程-Java
Spring Boot实战入门视频课程 国内上市大型医疗软件公司产品研发部总经理,技术培训总监.6年以上大型项目一线开发.架构.管理经验,曾主导医疗大数据+移动BI产品设计与研发.技术狂热爱好者,擅长 ...
- 视频教程-Spring boot快速入门-Java
Spring boot快速入门 十年项目开发经验,主要从事java相关的开发,熟悉各种mvc开发框架. 王振伟 ¥12.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 ...
- Spring Data ElasticSearch入门案例
Spring Data ElasticSearch入门案例 创建maven工程elasticsearch_springdata 基于maven导入坐标 导入spring data elasticsea ...
- spring boot(一)入门
目录 spring boot(一)入门 一.简介 1.微服务的概念 2.什么是spring boot 3.快速入门 4.springboot的快捷部署 spring boot(一)入门 一.简介 1. ...
最新文章
- 人工智能进入新的发展阶段
- Qt QPushButton圆形图片设置为背景
- 使用read_html爬取网页表哥,Python笔记:用read_html()爬取table形式表格的网络数据...
- mac下用scp命令实现本地文件与服务器Linux文件之间的相互传输
- 将一个项目中已有的文档添加到另一个项目中的方法
- Angular5 *ngIf 和 hidden 的区别
- spring 改变url
- pdo_mysql未安装_php pdo_mysql未安装问题解决方法
- 《游戏之旅-我的编程感悟》读书笔记
- openlayers 加载高德底图
- 一天一小段js代码(no.4)
- 修复华为移动服务器,移动app云服务器异常
- 《数值分析》-- 高斯消去法与矩阵三角分解法(LU分解)
- CSS学习笔记 | CSS复合选择器
- 你炒的肉丝为何又柴又老又难吃?
- 桂 林 理 工 大 学实 验 报 告 实验名称实验六 函数
- springSecurity 中不能抛出异常UserNameNotFoundException 解析
- CentOS命令汇总
- (附源码)计算机毕业设计SSM流浪动物管理系统
- 前端优化之回流(Reflow)与重绘(Repaint)
热门文章
- java valueof_Java Short类valueOf()方法及示例
- 软件工程质量管理体系要求_软件质量管理| 软件工程
- Listener refused the connection with the following error 错误解决(最大连接数)
- 网页如何做到适应在手机上浏览
- JavaScript编写了一个计时器
- 在Oracle中CHAR,NCHAR,VARCHAR,VARCHAR2,NVARCHAR2这五种类型的区别
- mllib协同过滤 java实现_协同过滤(ALS)算法介绍及Spark MLlib调用实例(Scala/Java/Python)...
- hive启动报错:Exception in thread “main“ java.lang.NoSuchMethodError: com.google.common.base.Precondition
- linux nginx 安装出错,Linux Nginx安装以及可能出现错误
- oracle 数字类型行转列,oracle行转列