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的执行顺序》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java核心篇之HashMap--day6

Java核心篇之HashMap–day6 HashMap是一种键值对的数据结构&#xff0c;以数组与链表的形式&#xff08;key&#xff1a;value&#xff09;实现&#xff0c;查询性能和添加性能很好。他是通过将key进行hashcode&#xff08;&#xff09;映射函数来找到表中对应的位置。 HashM…

Spring Boot————Spring Data JPA简介

引言 JPA是Java 持久化API的缩写&#xff0c;是一套Java数据持久化的规范&#xff0c; Spring Data Spring Data项目的目的是为了简化构建基于Spring 框架应用的数据访问技术&#xff0c;包括对关系型数据库的访问支持。另外也包含非关系型数据库、Map-Reduce框架、云数据服…

LeetCode算法入门- Valid Parentheses -day11

LeetCode算法入门- Valid Parentheses -day11 题目描述&#xff1a; Given a string containing just the characters ‘(’, ‘)’, ‘{’, ‘}’, ‘[’ and ‘]’, determine if the input string is valid. An input string is valid if: Open brackets must be closed …

Spring Boot————Spring Boot启动流程分析

一、引言 Spring Boot 的启动虽然仅仅是执行了一个main方法&#xff0c;但实际上&#xff0c;运行流程还是比较复杂的&#xff0c;其中包含几个非常重要的事件回调机制。在实际生产开发中&#xff0c;有时候也会利用这些启动流程中的回调机制&#xff0c;做一些项目初始化的工…

LeetCode算法入门- Longest Valid Parentheses -day12

LeetCode算法入门- Longest Valid Parentheses -day12 Given a string containing just the characters ‘(’ and ‘)’, find the length of the longest valid (well-formed) parentheses substring. 题目描述&#xff1a; 题目的意思是给定一个括号序列的字符串&#xff…

Spring Boot————应用启动时的监听机制测试

引言 本文承接前面的《Spring Boot————Spring Boot启动流程分析》&#xff0c;主要测试一下ApplicationContextInitializer、SpringApplicationRunListener、ApplicationRunner、CommandLineRunner这四个接口实现之下的组件是何时在Spring Boot项目启动时创建并执行相关方…

LeetCode算法入门- Longest Common Prefix -day13

LeetCode算法入门- Longest Common Prefix -day13 题目描述&#xff1a; Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string “”. Example 1: Input: [“flower”,“flow”…

HTMLCSS————块元素与内联元素

一、块元素与div标签 div是一个块元素&#xff0c;块元素会独占一行&#xff0c;无论它的内容有多少&#xff0c;都会独占一整行。 类似的块元素还有&#xff1a;<p>、<h1>、<h2>、<h3> 然而&#xff0c;<div>标签没有任何语义&#xff0c;就…

LeetCode算法入门- Compare Version Numbers -day14

LeetCode算法入门- Compare Version Numbers -day14 题目描述&#xff1a; Compare two version numbers version1 and version2. If version1 > version2 return 1; if version1 < version2 return -1;otherwise return 0. You may assume that the version strings a…

HTMLCSS————CSS常用选择器及优先级

选择器优先级 内联样式&#xff08;1000&#xff09;> id选择器&#xff08;100&#xff09;> 类和伪类选择器&#xff08;10&#xff09; > 元素选择器&#xff08;1&#xff09;>通配 * 选择器&#xff08;0&#xff09;> 继承的样式 一、元素选择器 作用&…

LeetCode算法入门- Merge Two Sorted Lists -day15

LeetCode算法入门- Merge Two Sorted Lists -day15 题目描述&#xff1a; Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists. Example: Input: 1->2->4, 1->3-&g…

2018年度总结

2018年&#xff0c;已经成为过去式&#xff0c;这360多天依旧过的很快&#xff0c;快到当我手扶键盘回想这一年发生的点点滴滴时&#xff0c;都没有任何感慨。可能我天生是个无感之人&#xff0c;或许&#xff0c;这一年的时光&#xff0c;无数的事故、故事已经让我变得不那么感…

LeetCode算法入门- Generate Parentheses -day16

LeetCode算法入门- Generate Parentheses -day16 题目描述 Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses. For example, given n 3, a solution set is: [ “((()))”, “(()())”, “(())()”, “()(())”, …

2019技术学习规划

引言 前段时间总结了一下2018年的大事小情&#xff08;《2018年度总结》&#xff09;&#xff0c;整体来说还是正能量满满&#xff0c;阅读量涨得也是蛮快的。今天&#xff0c;抽出点时间思考了一下未来一年的规划。那作为技术人才&#xff0c;规划也自然都是技术相关的&#…

LeetCode算法入门- Remove Nth Node From End of List -day17

LeetCode算法入门- Remove Nth Node From End of List -day17 题目解释&#xff1a; Given a linked list, remove the n-th node from the end of list and return its head. Example: Given linked list: 1->2->3->4->5, and n 2. After removing the seco…

Spring Boot————默认缓存应用及原理

引言 应用程序的数据除了可以放在配置文件中、数据库中以外&#xff0c;还会有相当一部分存储在计算机的内存中&#xff0c;这部分数据访问速度要快于数据库的访问&#xff0c;因此通常在做提升数据访问速度时&#xff0c;会将需要提升访问速度的数据放入到内存中&#xff0c;…

LeetCode算法入门- Multiply Strings -day18

LeetCode算法入门- Multiply Strings -day18 题目介绍 Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string. Example 1: Input: num1 “2”, num2 “3” Output: “6” Exampl…

Linux下查看版本及系统信息

一、查看Linux发行版本 [rootlocalhost ~]# cat /etc/issue CentOS release 6.8 (Final) Kernel \r on an \m[rootlocalhost ~]# cat /etc/redhat-release CentOS release 6.8 (Final)二、查看Linux内核信息 [rootlocalhost ~]# uname -a Linux localhost.localdomain 2.6.32…

LeetCode算法入门- Search Insert Position -day19

LeetCode算法入门- Search Insert Position -day19 题目描述 Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. You may assume no duplicates in the array.…

Linux——VMware虚拟机安装CentOS步骤

一、下载CentOS.iso镜像 最地道的下载方式就是通过官网&#xff0c;大多数的网上连接会直接抛出网易、华为的镜像连接&#xff0c;实际上这些连接都可以在官网找到&#xff1a; 官网地址&#xff08;可直接百度搜索CentOS&#xff09;&#xff1a;https://www.centos.org/ 1…