Spring 核心 之 AOP
什么是 AOP
AOP(Aspect-Oriented Programming):面向切面编程,是对传统的面向对象编程的补充。
什么意思呢?
比如上图中,在不同的方法中,有许多相同的功能代码,那我们就可以把这些相同的功能代码抽取出来,放到类中,那么这个类就被叫做切面。
实际上,AOP 的原理就是利用了动态代理,当我们需要调用目标对象的时候,Spring 就会帮我们生成一个代理对象,将切面和核心的业务逻辑代码组装起来,形成完整的模块。即使我们将代码抽离出来,也并不会影响我们的正常使用。
这样做的好处是:
- 业务模块更简洁,只包含核心业务代码,在我们编码的时候就可以更加专注于核心代码的编写上。其次在维护、调试的时候也更加容易定位问题的所在。
- 当我们需要修改公共功能的代码时,只需修改公共功能所在的切面即可,不必再去一个一个的方法中修改。
注解配置
对于面向切面编程,我们可以使用 ASpectJ 框架,AspectJ 是 Java 社区里最完整最流行的 AOP 框架。
准备工作
因此我们想要使用 AscpectJ 框架,就必须要先导入依赖的 jar 包:
- aopalliance.jar
- aspectj.weaver.jar
- spring-aspects.jar
其次我们还需要将 aop Schema 命名空间添加到配置文件中。
基于注解的 AOP
接下来我们就可以使用注解实现 AOP 了。
面向切面编程,那么首先我们得有切面。上文说到切面就是一个类,那难道说我们创建了一个类,这个类就是一个切面吗?Spring 如何识别这是一个切面呢?
我们可以使用 @Aspect
注解,只要在对应的类上标注这个注解,那么此类就是一个切面。
切面中是一个一个的通知,一个切面中可以有多个通知,通知就是切面要完成的工作,在我们想要调用业务方法时,会将这些通知加入到业务方法中的某个位置,比如方法前、后等,从而形成一个完整的业务功能。通知在代码中的体现就是加了某种注解的 Java 方法。
AspectJ 一共支持 5 种类型的通知,它们对应的注解分别是:
- @Before:前置通知,在方法执行之前执行。
- @After:后置通知,在方法执行之后执行,无论方法中是否有异常都会执行。
- @AfterRunning:返回通知,在方法返回结果之后执行。
- @AfterThrowing:异常通知,在方法抛出异常之后执行。
- @Around:环绕通知,需要我们自己在方法中控制。
前置通知
(1)
@Before
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();Object [] args = joinPoint.getArgs();System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
(2)
配置了通知之后,还缺一样东西,把这些通知应用到哪些方法上呢,我们要告诉 Spring,所以我们还要配置切入点表达式。
例如:
@Before("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
以下是一些示例:
表达式 | 含义 |
---|---|
execution(* com.atguigu.spring.ArithmeticCalculator.*(…)) | ArithmeticCalculator 接口中声明的所有方法。第一个“*”代表任意修饰符及任意返回值。第二个“*”代表任意方法。“…”匹配任意数量、任意类型的参数。若目标类、接口与该切面类在同一个包中可以省略包名。 |
execution(public * ArithmeticCalculator.*(…)) | ArithmeticCalculator 接口的所有公有方法。 |
execution(public double ArithmeticCalculator.*(…)) | ArithmeticCalculator 接口中返回double类型数值的方法。 |
execution(public double ArithmeticCalculator.*(double, …)) | 第一个参数为double类型的方法。“…” 匹配任意数量、任意类型的参数。 |
execution(public double ArithmeticCalculator.*(double, double)) | 参数类型为 double,double 类型的方法。 |
execution (* *.add(int,…)) || execution(* *.sub(int,…)) | 切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。 |
(3)
除此之外,要想让这些注解起作用,还需要在配置文件中配置一样东西。在 Spring IOC 容器中启用 AspectJ 注解支持,需要在配置文件中定义一个空的 XML 元素:
<!-- 配置自动为匹配 aspectJ 注解的 Java 类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
当 Spring IOC 容器侦测到 bean 配置文件中的 <aop:aspectj-autoproxy>
元素时,会自动为与 AspectJ 切面匹配的 bean 创建代理。
JoinPoint 类
通过 JoinPoint 类我们就可以访问一些链接细节,比如当前方法的名称、参数等。
后置通知
@After()
public void afterMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();System.out.println("The method " + methodName + " ends");
}
返回通知
在返回通知中,可以访问到方法的返回值,只需要将 returning
属性加入到 @AfterReturning
注解中,返回值就会传给 returning
属性对应的值。此外还要在方法中添加一个同名参数。
@AfterReturning(value="declareJointPointExpression()",returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){String methodName = joinPoint.getSignature().getName();System.out.println("The method " + methodName + " ends with " + result);
}
异常通知
异常通知中,可以定义发生何种异常时,才执行异常通知,并且可以访问到异常对象。和返回通知类似,我们需要在注解中添加 throwing
属性,以及在方法中添加一个和 throwing
属性值同名的参数,此参数即指定了发生何种异常执行此通知。
/*** 在目标方法出现异常时会执行的代码.* 可以访问到异常对象;,且可以指定在出现特定异常时再执行通知代码*/
@AfterThrowing(value="declareJointPointExpression()",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){String methodName = joinPoint.getSignature().getName();System.out.println("The method " + methodName + " occurs excetion:" + e);
}
环绕通知
环绕通知类似于动态代理的全过程,需要我们手动控制在何时(方法前?后?)执行什么代码。
与上述通知不同的是:环绕通知的连接点参数类型必须是 ProceedingJoinPoint
,它是 JoinPoint
的子接口,如果想要执行被代理的方法,必须调用 ProceedingJoinPoint
的 proceed()
方法。此外,环绕通知还必须有返回值,返回值即为目标方法的返回值,即 ProceedingJoinPoint.proceed()
方法的返回值。
/*** 环绕通知需要携带 ProceedingJoinPoint 类型的参数. * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
*/
@Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){Object result = null;String methodName = pjd.getSignature().getName();try {//前置通知System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));//执行目标方法result = pjd.proceed();//返回通知System.out.println("The method " + methodName + " ends with " + result);} catch (Throwable e) {//异常通知System.out.println("The method " + methodName + " occurs exception:" + e);throw new RuntimeException(e);}//后置通知System.out.println("The method " + methodName + " ends");return result;
}
切入点可重用
如果我们在每一个注解的后面都指定切入点表达式,则非常麻烦,如果修改还需要一个一个修改。因此我们可不可以将切入点表达式抽离出来呢?
答案是:可以的。我们可以使用 @Pointcut
注解来配置统一的切入点表达式,我们只需要在一个方法上方用 @Pointcut
注解标注,其他的注解直接引用该方法名即可。
切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为 public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。
/*** 定义一个方法, 用于声明切入点表达式。 一般地,该方法中再不需要添入其他的代码。* 使用 @Pointcut 来声明切入点表达式。* 后面的其他通知直接使用方法名来引用当前的切入点表达式。*/
@Pointcut("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){}@Before("declareJointPointExpression()")
配置切面的优先级
如果我们有好几个切面,Spring 就不知道谁改先执行,谁该后执行。不过,我们可以明确指定它们之间的执行顺序,切面的优先级可以通过实现 Ordered
接口或利用 @Order
注解指定。
实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。@Order 注解也类似,数值越小,优先级越高。
@Aspect
@Order(1)
public class LoggingAspect {}
XML 配置
切面除了支持注解配置,还支持使用配置文件的方式来配置。不过正常情况下,基于注解的声明要优先于基于 XML 的声明。
(1)
和注解配置类似,首先我们也要配置一个切面。切面所在的类要先实例化。
<!-- 配置切面的 bean. -->
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean><!-- 配置 AOP -->
<aop:config><!-- 配置切面 --><aop:aspect ref="loggingAspect" order="2"></aop:aspect>
</aop:config>
(2)
第二步我们要配置切入点表达式。使用 <aop:pointcut>
标签,如果配置在 <aop:config>
标签下,则所有的切面都可使用,如果配置在 <aop:aspect>
标签下,则只能在此切面中使用。
<!-- 配置 AOP -->
<aop:config><!-- 配置切点表达式 --><aop:pointcut id="pointcut" expression="execution(*com.spring.aop.xml.ArithmeticCalculator.*(int, int))" />
</aop:config>
(3)
第三步就是配置各个通知了,每种通知都对应这不同的 aop
标签,在通知中可以使用 pointcut
属性来单独配置切入点表达式,也可以使用 pointcut-ref
属性来引用已经配置好的切入点表达式。
<!-- 配置切面的 bean. -->
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean><bean id="vlidationAspect" class="com.atguigu.spring.aop.xml.VlidationAspect"></bean><!-- 配置 AOP -->
<aop:config><!-- 配置切点表达式 --><aop:pointcut id="pointcut" expression="execution(* com.spring.aop.xml.ArithmeticCalculator.*(int, int))" /><!-- 配置切面及通知 --><aop:aspect ref="loggingAspect" order="2"><aop:before method="beforeMethod" pointcut-ref="pointcut"/><aop:after method="afterMethod" pointcut-ref="pointcut"/><aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/><aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/><!-- <aop:around method="aroundMethod" pointcut-ref="pointcut"/>--></aop:aspect> <aop:aspect ref="vlidationAspect" order="1"><aop:before method="validateArgs" pointcut-ref="pointcut"/></aop:aspect>
</aop:config>
Spring 核心 之 AOP相关推荐
- spring核心之AOP学习总结二
一:springAOP常用的注解 @Aspect:声明方面组件 @Before:声明前置通知 @After-returning:声明后置通知 @After:声明最终通知 @Around:声明环绕通知 ...
- Spring核心部分之AOP,aspectJ框架实现AOP,切入点表达式
1. 面向切面编程(Aspect Oriented Programming,AOP) AOP:以切面为核心,确定切面的执行时间以及执行位置,底层实现是动态代理. AOP的作用:①在不改动源代码的情况下 ...
- Spring核心机制IoC与AoP梳理
Spring核心机制IoC与AoP梳理 文章目录 Spring核心机制IoC与AoP梳理 IoC介绍 IoC案例介绍 pom文件中IoC环境引入 自己new对象方法举例(正转) IoC创建对象 基于X ...
- Spring核心AOP(面向切面编程)总结
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/75208354冷血之心的博客) 目录 1.AOP概念: 2.AOP特 ...
- 一文读懂Spring中的AOP机制
一.前言 这一篇我们来说一下 Spring 中的 AOP 机制,为啥说完注解的原理然后又要说 AOP 机制呢? 1.标记日志打印的自定义注解 @Target({ElementType.METHOD}) ...
- Spring核心——IOC处理器扩展
为什么80%的码农都做不了架构师?>>> 非侵入式框架 Spring一直标注自己是一个非侵入式框架.非侵入式设计的概念并不新鲜,目标就是降低使用者和框架代码的耦合,毕竟框架的开 ...
- spring核心功能结构
spring核心功能结构 Spring大约有20个模块,由1300多个不同的文件构成.这些模块可以分为: 核心容器.AOP和设备支持.数据访问与集成.Web组件.通信报文和集成测试等, 下面是 Spr ...
- Spring(IOC+AOP)
Spring 容器(可以管理所有的组件(类))框架: 核心关注:IOC和AOP: Test:Spring的单元测试模块: spring-test-4.0.0.RELEASE Core C ...
- Spring源码-AOP(六)-自动代理与DefaultAdvisorAutoProxyCreator
2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 源码解析系列,建议大家按顺序阅读,欢迎讨论 Spring源码-AOP(一)-代理模式 Spring源码- ...
- 小马哥spring编程核心思想_Spring源码高级笔记之——Spring核心思想
Spring核心思想 注意:IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技术层次把这两个思想做了非常好的实现(Java) 第1节loC 1. ...
最新文章
- 用Zabbix部署onalert云告警平台
- python中fn的用法_Pytorch技巧:DataLoader的collate_fn参数使用详解
- java中executorservice_java中ExecutorService创建方法总结
- 先装VS2008再装SQL2005的问题
- 【必看】做了3年运维却不涨薪?那是你还没get这个技能
- 超级计算机的电力消耗,适用超级计算机的一种优化供电方式
- kubeadm reset后安装遇到的错误:Unable to connect to the server: x509: certificate signed by unknown authority
- Java与Python的区别告诉你,学什么看自己
- sre8 sre10_是什么使SRE出色?
- 秋叶一键重装系统连接服务器失败,如何解决远程桌面无法连接
- 时间操作(Java版)—获取距离系统时间N天后的日期时间信息
- windows下mongodb安装与使用
- B key-M key-BM key
- 红外图像特点及识别方法
- 黑客工具包ShadowBrokers浅析
- codesys工程ST语言学习笔记(一)建立工程与编译
- 【软件测试】时制转换时的电话账单
- 手机的1G,2G,3G和4G区别识记
- REVA世界巡回交流会——亚太峰会 澳门站拉开序幕
- 脑洞大开的思维工具:PMI
热门文章
- WebGL入门(十二)-使用varying变量在顶点着色器给片元着色器传值,绘制彩色三角形
- 程序员鄙视链, 所有工程师都鄙视php工程师, 为什么
- 【渝粤教育】国家开放大学2019年春季 1366英语教学理论与实践 参考试题
- 联想服务器rd640性能,至强E5芯动力 联想RD640服务器评测
- 7步走!做出高质量的数据分析项目
- 微信小程序:微信公众号申请微信小程序并认证的步骤
- 基于Vue使用Arco Design组件封装一个七牛云上传图片的函数
- 博客备份系统之一:PDF,Word,TXT文件操作类
- 出现Illegal invocation的报错
- C/C++编程学习 - 第22周 ② 非负数的和