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,一经查实,立即删除!

相关文章

Spring Boot————Spring Data JPA简介

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

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

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

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

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

2018年度总结

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

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——VMware虚拟机安装CentOS步骤

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

Spring Boot——Redis安装配置与应用整合

引言 Spring Boot默认以ConcurrentHashMap作为缓存容器&#xff0c;但默认的缓存容器在简单的场景使用还是可以的&#xff0c;而作为NoSQL的代表&#xff0c;Redis可以做内存数据库、消息中间件都是不错的&#xff0c;而且有RedisDesktopManager作为可视化管理工具&#xff0c…

利用Aria2高速下载网盘文件

利用Aria2高速下载网盘文件 方法步骤&#xff1a; 下载文件 解压arial2&#xff0c;运行aria2启动.VBS添加插件&#xff0c;解压BaiduExporter-master.zip在Google浏览器扩展程序中chrome://extensions加载已经解压的扩展程序 选择BaiduExporter进行添加即可&#xff0c;打开…

MySQL——JSON_REPLACE()函数修改JSON属性值

引言 由于对mysql的函数并不了解&#xff0c;之前遇到了一个场景&#xff1a; mysql表中有一个字段res_content 是一个由longtext类型&#xff08;可以理解为一个更长的varchar&#xff09;保存的巨大的JSON对象&#xff0c;但是&#xff0c;由于录入的疏忽&#xff0c;导致这…

Spring Boot整合Redis——自定义RedisSerializer

引言 spring boot简单引入redis依赖&#xff0c;并使用RedisTemplate进行对象存储时&#xff0c;需要使存储对象实现Serializable接口&#xff0c;这样才能够成功将对象进行序列化。 RedisTemplate默认使用的序列化机制是JdkSerializationRedisSerializer&#xff0c;但实际开…

交易系统如何确保账簿100%准确

转自廖雪峰老师的《交易系统如何确保账簿100%准确》 这篇文章阐述了一个交易系统中对账功能的关键&#xff0c;即&#xff1a;时刻保证资产负债表总额始终为 0。 交易系统中&#xff0c;对账是一个大问题。对账处理不好&#xff0c;不但需要花费大量的人力去处理账簿&#xff…

通俗易懂的SpringBoot教程---day1---Springboot入门教程介绍

通俗易懂的SpringBoot教程—day1—教程介绍 教程介绍&#xff1a; 初级教程&#xff1a; 一、 Spring Boot入门 二、 Spring Boot配置 三、 Spring Boot与日志 四、 Spring Boot与Web开发 五、 Spring Boot与Docker&#xff1a;Docker容器 六、 Spring Boot与数据访问&#x…

Java 8中获取参数名称

本文转自廖雪峰老师的&#xff1a;《在Java 8中获取参数名称》 在Java 8之前的版本&#xff0c;代码编译为class文件后&#xff0c;方法参数的类型是固定的&#xff0c;但参数名称却丢失了&#xff0c;这和动态语言严重依赖参数名称形成了鲜明对比。现在&#xff0c;Java 8开始…

通俗易懂的SpringBoot教程---day2---Springboot配置文件

通俗易懂的SpringBoot教程—day2—Springboot配置文件 1、配置文件 SpringBoot使用一个全局的配置文件&#xff0c;配置文件名是固定的&#xff1b; •application.properties •application.yml 配置文件的作用&#xff1a;修改SpringBoot自动配置的默认值&#xff1b;Spring…

Could not resolve host: 'localhost 报错解决办法

Could not resolve host: localhost 报错解决办法 面向Windows的&#xff1a; 零基础的我一直卡在这一步骤下&#xff1a; 首先要先在Windows安装curl&#xff1a;安装方式参考&#xff1a;https://blog.csdn.net/weixin_41986096/article/details/86646365 按照完之后&…

当面试官问我————为什么String是final的?

面试官&#xff1a;你好&#xff0c;能看得清下面这张图吗&#xff1f; 我&#xff1a;可以的。 面试官&#xff1a;恩&#xff0c;好的。呃&#xff0c;你能不能说一说为什么String要用final修饰&#xff1f; 我&#xff1a;final意味着不能被继承或者被重写&#xff0c;Str…

当面试官问我————Java是值传递还是引用传递?

面试官&#xff1a;你好&#xff0c;你能说出下面个程序的执行结果吗&#xff1f; public class Test {public static void main(String[] args) {String name "Scott";int age 5;User user new User();user.setName(name);user.setAge(age);System.out.println(…

ubuntu系统下Jenkins和tomcat的安装与配置

ubuntu 安装 JDK ubuntu的安装我们采取最简单的方式安装 直接用apt-get的方式 sudo apt-get install openjdk-8-jdk 安装器会提示你同意 oracle 的服务条款,选择 ok 然后选择yes 即可 ubuntu 安装tomcat8 通过apt安装 tomcat8 sudo apt-get install tomcat8 tomcat8-docs t…

String字符串拼接小例

>>>写出下面程序运行结果&#xff1a; public class StringTest {public static void main(String[] args) {String s1 "Programming";String a "Program";String b "ming";String s2 "Program" "ming";Stri…