从XML配置角度理解Spring AOP
本文分享自华为云社区《Spring高手之路18——从XML配置角度理解Spring AOP》,作者: 砖业洋__。
1. Spring AOP与动态代理
1.1 Spring AOP和动态代理的关系
Spring AOP使用动态代理作为其主要机制来实现面向切面的编程。这种机制允许Spring在运行时动态地创建代理对象,这些代理对象包装了目标对象(即业务组件),以便在调用目标对象的方法前后插入额外的行为(如安全检查、事务管理、日志记录等)。
JDK动态代理:当目标对象实现了一个或多个接口时,
Spring AOP默认使用JDK的动态代理。JDK动态代理通过反射机制,为接口创建一个代理对象,这个代理对象会拦截对目标接口方法的所有调用。CGLIB代理:如果目标对象没有实现任何接口,
Spring AOP会退回到使用CGLIB库生成目标类的子类。CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它在运行时扩展了Java类,并在子类中覆盖了方法来实现方法拦截。
无论使用哪种代理方式,目的都是在不改变原有业务逻辑代码的基础上,通过切面定义的通知在方法执行的不同阶段插入附加行为。
1.2 AOP基本术语
切面(Aspect):切面是面向切面编程的核心,它是将横跨多个类的关注点(如日志记录、事务管理等)模块化的构造。一个切面可以包含多种类型的通知(Advice)和一个或多个切点(Pointcut),用于定义在何处以及何时执行这些通知。
连接点(Join Point):连接点代表程序执行过程中的某个特定位置,Spring AOP限定这些位置为方法的调用。简而言之,连接点就是能够插入切面通知的点。
通知(Advice):通知定义了切面在连接点上要执行的动作。根据通知类型的不同,这些动作可以在方法调用之前、之后、返回结果后或抛出异常时执行。通知类型包括:
- 前置通知(
Before advice):在方法执行之前执行。 - 后置通知(
After advice):在方法执行后执行,无论其结果如何。 - 返回后通知(
After-returning advice):在方法成功执行之后执行。 - 异常后通知(
After-throwing advice):在方法抛出异常后执行。 - 环绕通知(
Around advice):在方法执行之前和之后执行,提供对方法调用的全面控制。
切点(Pointcut):切点是一个表达式,切点表达式允许通过方法名称、访问修饰符等条件来匹配连接点,决定了通知应该在哪些方法执行时触发。
目标对象(Target Object):被一个或多个切面所通知的对象。也被称为被代理对象。
AOP代理(AOP Proxy):AOP框架创建的对象,用于实现切面契约(由通知和切点定义)。在Spring AOP中,AOP代理可以是JDK动态代理或CGLIB代理。
引入(Introduction):引入允许向现有的类添加新的方法或属性。这是通过定义一个或多个附加接口(Introduction interfaces)实现的,AOP框架会为目标对象创建一个代理,该代理实现这些接口。
如果还是觉得抽象,我们再举一个电影制作的例子来类比
切面(Aspect)
想象一下,有人正在拍摄一部电影,而电影中的特效(比如爆炸和特殊光效)就像是应用程序中需要处理的横切关注点(比如日志记录或事务管理)。这些特效会在电影的许多不同场景中出现,而不仅仅局限于某一个特定场景。在AOP中,这些“特效”就是切面,它们可以被应用到程序的多个部分,而不需要改变实际的场景(或代码)。
连接点(Join Point)
继续使用电影的比喻,每个场景中的特定时刻,比如一个爆炸发生的瞬间,可以看作是一个连接点。在编程中,这通常对应于方法的调用。
通知(Advice)
通知就像是导演对特效团队的具体指令,比如“在这个场景开始之前加入一个爆炸效果”或“场景结束后显示烟雾渐散的效果”。这些指令告诉特效团队在电影的哪个具体时刻应该添加特定的效果。在AOP中,这些“指令”就是通知,指定了切面(特效)应该在连接点(特定的代码执行时刻)之前、之后或周围执行。
切点(Pointcut)
如果说通知是导演对特效团队的指令,那么切点就是指令中包含的具体条件,比如“所有夜晚的外景戏”。切点定义了哪些连接点(比如哪些具体的方法调用)应该接收通知(特效指令)。
目标对象(Target Object)
目标对象就是那些需要添加特效的场景。在我们的编程比喻中,它们是那些被切面逻辑影响的对象(比如需要日志记录的类)。
AOP代理(AOP Proxy)
AOP代理就像是特效团队提供的一个虚拟的、可控制特效的场景副本。这个副本在观众看来与原场景无异,但实际上它能在导演需要的时刻自动添加特效。在编程中,代理是一个被AOP框架自动创建的对象,它包装了目标对象,确保了通知(特效指令)在正确的时间被执行。
引入(Introduction)
引入就好比是在电影中加入一个全新的角色或者场景,这在原本的脚本中并不存在。在AOP中,引入允许我们向现有的类添加新的方法或属性,这就像是在不改变原始脚本的情况下扩展电影的内容。
2. 通过XML配置实现Spring AOP
Spring提供了丰富的AOP支持,可以通过XML配置来定义切面、通知(advice)和切点(pointcuts)。这样可以在不修改源代码的情况下增加额外的行为(如日志、事务管理等)
实现步骤:
添加Spring依赖:在项目的
pom.xml中添加Spring框架和AOP相关的依赖。定义业务接口和实现类:创建业务逻辑接口及其实现,比如一个简单的服务类。
定义切面类:创建一个切面类,用于定义前置、后置、环绕等通知。
配置XML:在
applicationContext.xml中配置切面和业务bean,以及AOP相关的标签。
2.1 添加Spring依赖
在pom.xml文件中,添加以下依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
2.2 定义业务接口和实现类
首先,我们定义一个业务逻辑接口MyService和它的实现MyServiceImpl。
MyService.java:
package com.example.demo.aop;
public interface MyService {
String performAction(String input) throws Exception;
}
MyServiceImpl.java:
package com.example.demo.aop;
public class MyServiceImpl implements MyService {
@Override
public String performAction(String action) throws Exception {
System.out.println("Performing action in MyService: " + action);
if ("throw".equals(action)) {
throw new Exception("Exception from MyService");
}
return "Action performed: " + action;
}
}
2.3 定义切面类
接下来,我们定义一个切面类MyAspect,这个类将包含一个前置通知(advice),它在MyService的performAction方法执行之前执行。
MyAspect.java:
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("Before advice is running!");
}
// 后置通知
public void afterAdvice() {
System.out.println("After advice is running!");
}
// 返回后通知
public void afterReturningAdvice(Object retVal) {
System.out.println("After returning advice is running! Return value: " + retVal);
}
// 异常后通知
public void afterThrowingAdvice(Throwable ex) {
System.out.println("After throwing advice is running! Exception: " + ex.getMessage());
}
// 环绕通知
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around advice: Before method execution");
Object result = null;
try {
result = joinPoint.proceed();
} finally {
System.out.println("Around advice: After method execution");
}
return result;
}
}
2.4 配置XML
最后,我们需要在Spring的配置文件applicationContext.xml中配置上述bean以及AOP的相关内容。
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
上面这是XML文件的头部声明,它定义了文件的版本和编码类型,同时引入了Spring beans 和 AOP 的命名空间。
通过这些命名空间,我们可以在XML中使用<bean>和<aop:*>标签。
-->
<!-- Bean definitions -->
<bean id="myService" class="com.example.demo.aop.MyServiceImpl"/>
<bean id="myAspect" class="com.example.demo.aop.MyAspect"/>
<!-- AOP配置 -->
<aop:config>
<!-- 定义切面及其通知 -->
<aop:aspect id="myAspectRef" ref="myAspect">
<!-- 定义切点,指定通知应该在哪些方法执行时触发 -->
<aop:pointcut id="serviceOperation" expression="execution(* com.example.demo.aop.MyService.performAction(..))"/>
<!-- 应用前置通知,指定方法执行前的操作 -->
<aop:before method="beforeAdvice" pointcut-ref="serviceOperation"/>
<!-- 应用后置通知,指定方法执行后的操作,不论方法执行成功还是抛出异常 -->
<aop:after method="afterAdvice" pointcut-ref="serviceOperation"/>
<!-- 应用返回后通知,指定方法成功执行并返回后的操作 -->
<aop:after-returning method="afterReturningAdvice" pointcut-ref="serviceOperation" returning="retVal"/>
<!-- 应用异常后通知,指定方法抛出异常后的操作 -->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="serviceOperation" throwing="ex"/>
<!-- 应用环绕通知,提供方法执行前后的完全控制 -->
<aop:around method="aroundAdvice" pointcut-ref="serviceOperation"/>
</aop:aspect>
</aop:config>
</beans>
myService:这是业务逻辑的bean,指向MyServiceImpl类的实例。
myAspect:这是切面的bean,指向MyAspect类的实例。
<aop:config>:这是AOP配置的根元素,所有的AOP配置,包括切面定义、切点和通知方法等,都需要在此元素内部定义。
切面(Aspect):通过<aop:aspect>元素定义,它包含了一系列通知(advice)和一个或多个切点(pointcut)。这个元素将切面类(包含通知逻辑的类)与具体的操作(如何、何时对目标对象进行增强)关联起来。
切点(Pointcut):通过<aop:pointcut>元素定义,切点通过表达式来指定,当需要精确控制哪些方法执行时会触发通知时,就需要定义切点。切点表达式可以非常精确地指定方法,例如通过方法名称、参数类型、注解等。expression定义了切点的表达式,指明了切点的匹配规则。这里的表达式execution(* com.example.demo.aop.MyService.performAction(..))意味着切点匹配MyService接口中performAction方法的执行,切点用于指定在哪些连接点(Join Point,例如方法调用)上应用通知。
关于解析表达式execution(* com.example.demo.aop.MyService.performAction(..))
execution:是最常用的切点函数,用于匹配方法执行的连接点。
*:表示方法的返回类型是任意的。
com.example.demo.aop.MyService.performAction:指定了全路径的接口名和方法名。
(…):表示方法参数是任意的,无论方法有多少个参数都匹配。
连接点(Join Point):连接点是指在程序执行过程中的某一点,比如方法的调用。 连接点是通过切点(
Pointcut)的表达式来识别和匹配的,execution(* com.example.demo.aop.MyService.performAction(..))表达式定义了一个切点,它指定了一个明确的连接点集合——即MyService接口的performAction方法的所有调用。这个例子中,MyService接口的performAction方法的调用就是潜在的连接点。每次performAction方法被调用时,就达到了一个连接点。这个连接点就是这里通知应用的时机。通知(Advice):这是
AOP通过在特定时机执行的操作来增强方法的执行。method属性指明当切点匹配时应该执行的切面的方法名,pointcut-ref引用了上面定义的切点。比如这里的beforeAdvice是在目标方法performAction执行之前被调用的方法。这意味着每当MyService.performAction(..)方法被调用时,beforeAdvice方法将首先被执行。
总结为一句话:Spring AOP通过在切面中定义规则(切点)来指定何时(连接点)以及如何(通知)增强特定方法,实现代码的模块化和关注点分离,无需修改原有业务逻辑。
通过这种方式,Spring AOP 允许定义在特定方法执行前、执行后、环绕执行等时机插入自定义逻辑,而无需修改原有业务逻辑代码。这是实现关注点分离的一种强大机制,特别是对于跨越应用程序多个部分的横切关注点(如日志、事务管理等)。
注意,如果<aop:config>设置为
<aop:config proxy-target-class="true">
<!-- 其他配置不变 -->
</aop:config>
设置proxy-target-class="true"会使Spring AOP优先使用CGLIB代理,即使目标对象实现了接口。默认情况下,不需要设置proxy-target-class属性,或者将其设置为false,则是使用JDK动态代理。
主程序:
DemoApplication.java:
package com.example.demo; import com.example.demo.aop.MyService;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class DemoApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService myService = (MyService) context.getBean("myService"); try {
System.out.println(myService.performAction("normal"));
} catch (Exception e) {
e.printStackTrace();
} System.out.println("======================="); try {
System.out.println(myService.performAction("throw"));
} catch (Exception e) {
System.out.println("Exception caught in main: " + e.getMessage());
} context.close();
}
}
运行结果:
通过结合动态代理技术和这些AOP概念,Spring AOP能够以非侵入式的方式为应用程序提供横切关注点的支持,这样开发者就可以将这些关注点模块化,并保持业务逻辑组件的聚焦和简洁。
如果对动态代理感兴趣可以再调试看看,这里是JDK动态代理是因为public class MyServiceImpl implements MyService 实现了接口,调试如下:
简单说一下这里能看到的关键类和接口
ProxyFactory: 这是Spring AOP用来创建代理对象的工厂类。它可以根据目标对象是否实现接口来决定使用JDK动态代理还是CGLIB代理。
AopProxy: 这个接口定义了获取代理对象的方法。它有两个主要实现:JdkDynamicAopProxy(用于JDK动态代理)和CglibAopProxy(用于CGLIB代理)。
JdkDynamicAopProxy: 实现了AopProxy接口,使用JDK动态代理技术创建代理。它实现了InvocationHandler接口,拦截对代理对象的所有方法调用。
CglibAopProxy: 同样实现了AopProxy接口,但使用CGLIB库来创建代理对象。对于没有实现接口的类,Spring会选择这种方式来创建代理。
如果大家想深入了解Spring AOP的源码,可以直接查看JdkDynamicAopProxy和CglibAopProxy这两个类的实现。这里不是本篇重点,简单提一下:
比如在JdkDynamicAopProxy中看到动态代理的实现:
JdkDynamicAopProxy类实现了InvocationHandler接口,这是JDK动态代理的核心。在其invoke方法中,会有逻辑判断是否需要对调用进行拦截,并在调用前后应用相应的通知。创建代理的过程主要是在
ProxyFactory通过调用createAopProxy()方法时完成的,这个方法会根据配置返回JdkDynamicAopProxy或CglibAopProxy的实例。代理的使用:客户端代码通过
ProxyFactory获取代理对象,并通过这个代理对象调用目标方法。代理对象在内部使用JdkDynamicAopProxy或CglibAopProxy来拦截这些调用,并根据AOP配置执行通知。通过ProxyFactory获取代理对象的过程,通常在Spring的配置和使用中是隐式完成的,特别是在使用Spring容器管理AOP时。这一过程不需要开发者直接调用ProxyFactory类。当Spring配置中定义了一个bean,并对其应用了切面,Spring容器会自动处理代理的创建和应用通知的过程。这是通过Spring的后处理器和AOP命名空间的支持实现的,开发者通常只需声明式地配置切面和通知即可。
如果想看到CGLIB代理,这里有2种方法
第1种方法是去掉MyServiceImpl实现的MyService接口,然后把主程序和expression表达式对应的地方改成MyServiceImpl。
第2种方法就是Spring配置文件中显式设置<aop:config>标签的proxy-target-class="true"属性来实现这一点。如下:
<aop:config proxy-target-class="true">
<!-- 其他配置保持不变 -->
</aop:config>
调试如下:
欢迎一键三连~
有问题请留言,大家一起探讨学习
从XML配置角度理解Spring AOP的更多相关文章
- spring 理解Spring AOP 一个简单的约定游戏
应该说AOP原理是Spring技术中最难理解的一个部分,而这个约定游戏也许会给你很多的帮助,通过这个约定游戏,就可以理解Spring AOP的含义和实现方法,也能帮助读者更好地运用Spring AOP ...
- 深入理解Spring AOP之二代理对象生成
深入理解Spring AOP之二代理对象生成 spring代理对象 上一篇博客中讲到了Spring的一些基本概念和初步讲了实现方法,当中提到了动态代理技术,包含JDK动态代理技术和Cglib动态代理 ...
- 曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 轻松理解 Spring AOP
目录 Spring AOP 简介 Spring AOP 的基本概念 面向切面编程 AOP 的目的 AOP 术语和流程 术语 流程 五大通知执行顺序 例子 图例 实际的代码 使用 Spring AOP ...
- 深入理解Spring AOP 1.0
本文相关代码(来自官方源码spring-test模块)请参见spring-demysify org.springframework.mylearntest包下. 统称能够实现AOP的语言为AOL,即( ...
- 理解Spring AOP的实现方式与思想
Spring AOP简介 如果说IOC是Spring的核心,那么面向切面编程就是Spring最核心的功能之一了,在数据库事务中,面向切面编程被广泛应用. AOP能够将那些与业务无关,却为业务模块所共同 ...
- 正确理解Spring AOP中的Around advice
Spring AOP中,有Before advice和After advice,这两个advice从字面上就可以很容易理解,但是Around advice就有点麻烦了. 乍一看好像是Before ad ...
- 深入理解Spring AOP思想
什么是AOP?AOP解决了什么问题? 在传统的开发模式中,以下层次的是非常常见的一种,业务层每一个方法都要有重复的事务代码 如何改善这个问题? AOP希望将A.B 这些分散在各个业务逻辑中的相同代码, ...
- web.xml配置重理解
<context-param> <param-name>home-page</param-name> <param-value>home.jsp< ...
- Spring-AOP SpringBoot自动配置和启动Spring AOP
SpringBoot 会使用 @Conditional* 注解来进行判断是否需要自动启动 AOP,如果 classpath 下有 spring-aop 的 jar 和有 EnableAspectJAu ...
随机推荐
- 深入理解HashMap和TreeMap的区别
目录 简介 HashMap和TreeMap本质区别 排序区别 Null值的区别 性能区别 共同点 深入理解HashMap和TreeMap的区别 简介 HashMap和TreeMap是Map家族中非常常 ...
- 小师妹学JavaIO之:文件编码和字符集Unicode
目录 简介 使用Properties读取文件 乱码初现 字符集和文件编码 解决Properties中的乱码 真.终极解决办法 总结 简介 小师妹一时兴起,使用了一项从来都没用过的新技能,没想却出现了一 ...
- C++ 面试必备:常见 C++ 面试题汇总及详细解析
C++作为一门重要的编程语言,其在面试中常常是热门的考察对象.本文将会介绍一些常见的C++面试题,帮助C++面试者避免很多不必要的困惑和迷惑.每个问题都有相对应的答案,以便各位同学快速查阅. C++和 ...
- Ascend C 自定义算子 Kernel Launch调用入门
本文分享自华为云社区<Ascend C 自定义算子 Kernel Launch调用入门>,作者: jackwangcumt. 1 Kernel Launch概述 根据官方说明文档的介绍,A ...
- 构筑立体世界,AR Engine助力B站会员购打造沉浸式营销
随着购物场景的逐渐多元化,越来越多电商平台把线下购物体验搬到线上,运用AR技术,跨越空间距离,帮助用户在购买前"体验"商品,增强购买意愿. 哔哩哔哩会员购(后称会员购)是B站于20 ...
- openGauss资源池化开发者入门指南(一)
openGauss资源池化开发者入门指南(一) 一.内容简介 openGauss 资源池化是 openGauss 推出的一种新型的集群架构.通过 DMS 和 DSS 组件,实现集群中多个节点的底层存储 ...
- HDC2022 开发者亮点抢先看,线上线下精彩活动等你探索!
原文:https://mp.weixin.qq.com/s/A2sfpPKBvF6zwinbUsgwiw,点击链接查看更多技术内容.
- 带你走进红帽企业级 Linux 6体验之旅(安装篇)
红帽在11月10日发布了其企业级Linux,RHEL 6的正式版(51CTO编辑注:红帽官方已经不用RHEL这个简称了,其全称叫做Red Hat Enterprise Linux).新版带来了将近18 ...
- Causal Inference理论学习篇-Tree Based-Causal Forest
广义随机森林 了解causal forest之前,需要先了解其forest实现的载体:GENERALIZED RANDOM FORESTS[6](GRF) 其是随机森林的一种推广, 经典的随机森林只能 ...
- vue 弹出框挂载富文本,富文本()无法实例化问题
因为elementUI的dialog组件,他是v-if渲染,再页面刚加载时,这个弹出框还未进行挂载,我们就让他v-show显示. ,富文本实例再mounted里面挂载即可