Spring AOP的核心原理和两种实现方式全解析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、为什么需要AOP?从登录功能增强场景说起</li><li>二、AOP核心概念深度拆解</li><ul class="second_class_ul"><li>2.1 什么是AOP?</li><li>2.2 AOP的核心优势</li><li>2.3 AOP底层实现原理</li><ul class="third_class_ul"><li>1. JDK动态代理</li><li>2. CGLIB动态代理</li></ul></ul><li>三、Spring AOP实战:配置文件方式</li><ul class="second_class_ul"><li>3.1 先搞懂AOP的核心术语</li><ul class="third_class_ul"></ul><li>3.2 实战准备:环境搭建</li><ul class="third_class_ul"></ul><li>3.3 实战步骤:实现方法增强</li><ul class="third_class_ul"><li>步骤1:创建被增强的目标类(核心业务类)</li><li>步骤2:将目标类交给Spring管理</li><li>步骤3:创建切面类(增强逻辑载体)</li><li>步骤4:将切面类交给Spring管理</li><li>步骤5:配置AOP(绑定切入点和通知)</li><li>步骤6:编写测试类验证结果</li></ul><li>3.4 切入点表达式详解</li><ul class="third_class_ul"></ul><li>3.5 五种通知类型的实现</li><ul class="third_class_ul"><li>1. 前置通知(@Before)</li><li>2. 环绕通知(@Around)</li><li>3. 最终通知(@After)</li><li>4. 后置通知(@AfterReturning)</li><li>5. 异常通知(@AfterThrowing)</li></ul></ul><li>四、Spring AOP实战:注解方式(更简洁高效)</li><ul class="second_class_ul"><li>4.1 注解方式核心步骤</li><ul class="third_class_ul"><li>步骤1:环境准备</li><li>步骤2:开启注解扫描和AOP自动代理</li><li>步骤3:使用注解标识目标类和切面类</li><li>步骤4:使用注解配置通知和切入点</li><li>步骤5:编写测试类验证</li></ul></ul><li>五、总结:两种AOP实现方式对比与适用场景</li><ul class="second_class_ul"></ul></ul></div><p>在Spring框架的学习旅程中,AOP(面向切面编程)绝对是核心重点之一。它打破了传统纵向编程的思维局限,通过横向抽取机制解决了代码冗余、耦合度高的痛点。本文将从AOP的概念引入出发,层层拆解核心原理,再通过完整实战案例覆盖配置文件和注解两种实现方式,帮你彻底掌握Spring AOP的应用精髓。</p><p class="maodian"></p><h2>一、为什么需要AOP?从登录功能增强场景说起</h2>
<p>要理解AOP的价值,我们先从一个常见的业务场景切入——登录功能的增强。在基础的登录功能实现中,核心逻辑是校验用户账号密码的合法性,验证通过后即可完成登录流程。</p>
<p>但随着业务迭代,我们往往需要在登录功能之上叠加新的需求,比如「权限校验」:不同角色的用户登录后能访问的资源不同,需要在登录后额外判断角色权限。此时,我们有两种实现思路:</p>
<ul><li>直接修改登录功能的源代码,在原有逻辑中嵌入权限校验代码;</li><li>不改动原有登录代码,通过外部机制为登录功能附加权限校验能力。</li></ul>
<p>第一种方案看似直接,却存在诸多问题:一旦核心业务逻辑(如登录)需要叠加多个增强功能(权限校验、日志记录、缓存处理等),源代码会变得臃肿不堪,后续维护难度剧增;同时,这些增强功能在多个业务模块中可能重复出现,导致代码冗余。</p>
<p>而第二种方案正是AOP的核心思想——<strong>在不修改原有业务代码的前提下,对程序功能进行增强</strong>。这种方式能实现业务逻辑与增强逻辑的解耦,让代码结构更清晰、可维护性更强。</p>
<p class="maodian"></p><h2>二、AOP核心概念深度拆解</h2>
<p class="maodian"></p><h3>2.1 什么是AOP?</h3>
<p>AOP全称为Aspect Oriented Programming,即面向切面编程。它并非一种具体的技术,而是一种编程范式,隶属于软件工程范畴,指导开发者如何更合理地组织程序结构。</p>
<p>AOP的思想最早由AOP联盟提出并制定了相关规范,Spring框架引入AOP思想时完全遵循该规范。其核心是通过<strong>预编译</strong>或<strong>运行期动态代理</strong>技术,实现程序功能的统一维护。</p>
<p>作为OOP(面向对象编程)的延续,AOP弥补了OOP在横向功能扩展上的不足。OOP通过继承和封装实现纵向的功能复用,而AOP通过横向抽取机制,将分散在各个业务模块中的重复代码(如事务管理、安全检查、日志记录、缓存处理等)抽取出来,形成独立的「切面」,再动态植入到需要增强的业务方法中。</p>
<p>一句话总结AOP的价值:<strong>隔离业务逻辑与增强逻辑,降低耦合度,提高代码可重用性和开发效率</strong>。</p>
<p class="maodian"></p><h3>2.2 AOP的核心优势</h3>
<p>基于AOP的设计思想,其核心优势主要体现在三个方面:</p>
<ul><li>减少重复代码:将日志、权限、事务等通用增强逻辑抽取为切面,避免在多个业务模块中重复编写;</li><li>提升开发效率:开发者只需专注于核心业务逻辑的实现,通用增强功能直接复用已有的切面,无需重复开发;</li><li>便于维护:当通用增强逻辑需要修改时(如日志格式调整),只需修改对应的切面代码,无需改动所有业务模块,维护成本大幅降低。</li></ul>
<p class="maodian"></p><h3>2.3 AOP底层实现原理</h3>
<p>Spring AOP的底层依赖两种动态代理技术实现,具体使用哪种技术取决于被增强的类是否实现接口:</p>
<p class="maodian"></p><h4>1. JDK动态代理</h4>
<p>当被增强的类实现了接口时,Spring会采用JDK动态代理技术生成代理对象,核心步骤如下:</p>
<ul><li>为被代理类的接口生成代理类的字节码文件;</li><li>通过类加载器(ClassLoader)将生成的字节码文件加载到JVM中;</li><li>创建代理类的实例对象,当调用代理对象的方法时,会触发增强逻辑的执行,再调用目标方法。</li></ul>
<p class="maodian"></p><h4>2. CGLIB动态代理</h4>
<p>当被增强的类没有实现任何接口时,Spring会采用CGLIB动态代理技术。其核心原理是<strong>生成被代理类的子类作为代理对象</strong>,通过重写父类的方法植入增强逻辑。由于是基于继承实现的,被代理类不能是final修饰的(final类无法被继承)。</p>
<p class="maodian"></p><h2>三、Spring AOP实战:配置文件方式</h2>
<p>Spring AOP的实现方式有两种:配置文件方式(XML)和注解方式。我们先从配置文件方式入手,通过完整的实战案例理解AOP的核心术语和实现流程。</p>
<p class="maodian"></p><h3>3.1 先搞懂AOP的核心术语</h3>
<p>在开始实战前,必须先明确AOP的几个核心术语,否则会对配置逻辑感到困惑:</p>
<ul><li>Joinpoint(连接点):类中可以被增强的所有方法都称为连接点(即哪些方法有增强的可能性);</li><li>Pointcut(切入点):从所有连接点中筛选出的、真正需要被增强的方法(即明确要对哪个方法进行增强);</li><li>Advice(通知/增强):拦截到切入点方法后要执行的逻辑(如权限校验、日志记录等),根据执行时机不同分为前置通知、后置通知、环绕通知、最终通知、异常通知;</li><li>Aspect(切面):切入点和通知的结合体,是AOP核心逻辑的载体(即「对哪个方法」+「做什么增强」的组合)。</li></ul>
<p class="maodian"></p><h3>3.2 实战准备:环境搭建</h3>
<p>本案例基于Maven构建项目,首先需要引入相关依赖。Spring AOP的实现依赖Spring核心包、AOP联盟规范包、AspectJ相关包(用于解析切入点表达式)等,具体依赖如下:</p>
<div class="jb51code"><pre class="brush:plain;"><dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--AOP联盟规范包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--Spring Aspects包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--AspectJ织入包(解析切入点表达式)-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
</dependencies></pre></div>
<p class="maodian"></p><h3>3.3 实战步骤:实现方法增强</h3>
<p class="maodian"></p><h4>步骤1:创建被增强的目标类(核心业务类)</h4>
<p>创建一个User类,包含add和update两个核心业务方法,这两个方法就是我们潜在的连接点:</p>
<div class="jb51code"><pre class="brush:java;">// 被增强的目标类(核心业务类)
public class User {
// 连接点/切入点
public void add(){
System.out.println("add......");
}
public void update(){
System.out.println("update......");
}
}</pre></div>
<p class="maodian"></p><h4>步骤2:将目标类交给Spring管理</h4>
<p>在Spring配置文件(applicationContext.xml)中配置目标类的Bean:</p>
<div class="jb51code"><pre class="brush:xml;"><bean id="user" class="com.aopImpl.User"></bean></pre></div>
<p class="maodian"></p><h4>步骤3:创建切面类(增强逻辑载体)</h4>
<p>创建UserProxy类作为切面类,在其中定义增强逻辑(通知)。这里先实现一个前置通知(目标方法执行前执行的增强逻辑):</p>
<div class="jb51code"><pre class="brush:java;">// 切面类(包含增强逻辑)
public class UserProxy {
// 前置通知:目标方法执行前执行
public void before(){
System.out.println("before.............");
}
}</pre></div>
<p class="maodian"></p><h4>步骤4:将切面类交给Spring管理</h4>
<p>在配置文件中配置切面类的Bean:</p>
<div class="jb51code"><pre class="brush:xml;"><bean id="userProxy" class="com.aopImpl.UserProxy"></bean></pre></div>
<p class="maodian"></p><h4>步骤5:配置AOP(绑定切入点和通知)</h4>
<p>在配置文件中通过<aop:config>标签完成切面的配置,核心是绑定切入点(要增强的方法)和通知(增强逻辑):</p>
<div class="jb51code"><pre class="brush:xml;"><!--AOP核心配置-->
<aop:config>
<!--配置切面:关联切面类(ref指定切面Bean的id)-->
<aop:aspect ref="userProxy">
<!--配置前置通知:指定通知方法(method)和切入点(pointcut)-->
<!--pointcut属性:通过切入点表达式指定要增强的方法-->
<aop:before method="before" pointcut="execution(public void com.aopImpl.User.add())"/>
</aop:aspect>
</aop:config></pre></div>
<p class="maodian"></p><h4>步骤6:编写测试类验证结果</h4>
<p>通过Spring容器获取目标类的Bean,调用add方法,观察是否执行增强逻辑:</p>
<div class="jb51code"><pre class="brush:java;">public class DemoTest {
@Test
public void aopTest1(){
// 加载Spring配置文件,初始化容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取User对象(实际是Spring生成的代理对象)
User user = (User) applicationContext.getBean("user");
// 调用add方法
user.add();
}
}</pre></div>
<p class="maodian"></p><h3>3.4 切入点表达式详解</h3>
<p>在AOP配置中,切入点表达式是核心,它的作用是精准定位要增强的方法。切入点表达式的完整格式如下:</p>
<p><code>execution([修饰符] [返回值类型] [类全路径] [方法名 ( [参数] )])</code></p>
<p>各部分说明及使用规则:</p>
<ul><li>修饰符:可选,如public、private等,可省略不写;</li><li>返回值类型:必填,必须与目标方法的返回值类型一致,可使用<code>*</code>表示任意返回值类型;</li><li>类全路径:必填,即目标类的包名+类名,可使用<code>*</code>模糊匹配(如<code>com.*.User</code>表示com包下任意子包中的User类);</li><li>方法名:必填,可使用<code>*</code>表示任意方法;</li><li>参数:必填,无参数写<code>()</code>,单个任意参数写<code>( * )</code>,任意个数、任意类型参数写<code>(..)</code>。</li></ul>
<p>常用切入点表达式示例:</p>
<ul><li>增强com.aopImpl.User类的add方法(无参数、返回值为void): <code>execution(void com.aopImpl.User.add())</code></li><li>增强com.aopImpl.User类的所有方法(任意返回值、任意参数): <code>execution(* com.aopImpl.User.*(..))</code></li><li>增强com包下所有子包中所有类的所有方法: <code>execution(* com.*.*.*(..))</code></li><li>增强所有ServiceImpl结尾的类的save方法(任意返回值、任意参数): <code>execution(* com.*.*ServiceImpl.save(..))</code></li></ul>
<p class="maodian"></p><h3>3.5 五种通知类型的实现</h3>
<p>除了前置通知,Spring AOP还支持五种通知类型,分别对应不同的执行时机。下面我们在UserProxy切面类中补充所有通知类型的实现,并完成配置:</p>
<p class="maodian"></p><h4>1. 前置通知(@Before)</h4>
<p>目标方法执行前执行,已在上面的案例中实现,配置方式:</p>
<div class="jb51code"><pre class="brush:xml;"><aop:before method="before" pointcut="execution(* com.*.User.add(..))"/></pre></div>
<p class="maodian"></p><h4>2. 环绕通知(@Around)</h4>
<p>目标方法执行前后都执行,需要手动调用<code>ProceedingJoinPoint</code>的<code>proceed()</code>方法触发目标方法执行:</p>
<div class="jb51code"><pre class="brush:java;">// 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知-前置增强.............");
// 手动执行目标方法
proceedingJoinPoint.proceed();
System.out.println("环绕通知-后置增强.............");
}</pre></div>
<p>配置方式: </p>
<div class="jb51code"><pre class="brush:xml;"><aop:around method="around" pointcut="execution(* com.*.User.add(..))"/></pre></div>
<p class="maodian"></p><h4>3. 最终通知(@After)</h4>
<p>目标方法无论执行成功还是失败,都会执行(<strong>类似try-catch中的finally块</strong>):</p>
<div class="jb51code"><pre class="brush:java;">// 最终通知
public void after() {
System.out.println("最终通知.............");
}</pre></div>
<p>配置方式:</p>
<div class="jb51code"><pre class="brush:xml;"><aop:after method="after" pointcut="execution(* com.*.User.add(..))"/></pre></div>
<p class="maodian"></p><h4>4. 后置通知(@AfterReturning)</h4>
<p>目标方法执行成功后才会执行,若目标方法抛出异常则不执行:</p>
<div class="jb51code"><pre class="brush:java;">// 后置通知
public void afterReturning() {
System.out.println("后置通知.............");
}</pre></div>
<p>配置方式: </p>
<div class="jb51code"><pre class="brush:java;"><aop:after-returning method="afterReturning" pointcut="execution(* com.*.User.add(..))"/></pre></div>
<p class="maodian"></p><h4>5. 异常通知(@AfterThrowing)</h4>
<p>目标方法执行失败(抛出异常)时才会执行:</p>
<div class="jb51code"><pre class="brush:java;">// 异常通知
public void afterThrowing() {
System.out.println("异常通知.............");
}</pre></div>
<p>为了验证异常通知,需要修改目标类的add方法,手动抛出异常:</p>
<div class="jb51code"><pre class="brush:java;">public void add(){
// 手动制造异常
int a = 10 / 0;
System.out.println("add......");
}</pre></div>
<p>配置方式: </p>
<div class="jb51code"><pre class="brush:java;"><aop:after-throwing method="afterThrowing" pointcut="execution(* com.*.User.add(..))"/></pre></div>
<p class="maodian"></p><h2>四、Spring AOP实战:注解方式(更简洁高效)</h2>
<p>配置文件方式的AOP虽然逻辑清晰,但配置项较多,对于复杂项目会显得繁琐。Spring提供了注解方式的AOP实现,通过注解可以快速完成切面的配置,更符合现代开发习惯。</p>
<p class="maodian"></p><h3>4.1 注解方式核心步骤</h3>
<p class="maodian"></p><h4>步骤1:环境准备</h4>
<p>依赖与配置文件方式一致,无需额外引入依赖。</p>
<p class="maodian"></p><h4>步骤2:开启注解扫描和AOP自动代理</h4>
<p>在Spring配置文件中添加注解扫描和AOP自动代理的配置,开启注解驱动的AOP功能:</p>
<div class="jb51code"><pre class="brush: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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描:指定要扫描的包(com.aopImpl)-->
<context:component-scan base-package="com.aopImpl"></context:component-scan>
<!--开启AOP自动代理:让Spring自动为切面类生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans></pre></div>
<p class="maodian"></p><h4>步骤3:使用注解标识目标类和切面类</h4>
<p>通过<code>@Component</code>注解将目标类和切面类交给Spring管理,通过<code>@Aspect</code>注解标识切面类:</p>
<div class="jb51code"><pre class="brush:java;">// 目标类:通过@Component交给Spring管理
@Component
public class User {
public void add(){
System.out.println("add......");
}
}
// 切面类:@Component交给Spring管理,@Aspect标识为切面类
@Component
@Aspect
public class UserProxy {
// 增强逻辑将在下面通过注解配置
}</pre></div>
<p class="maodian"></p><h4>步骤4:使用注解配置通知和切入点</h4>
<p>Spring提供了对应的注解来配置五种通知类型,注解的<code>value</code>属性用于指定切入点表达式:</p>
<div class="jb51code"><pre class="brush:java;">@Component
@Aspect
public class UserProxy {
// 1. 前置通知:@Before
@Before(value = "execution(* com.*.User.add(..))")
public void before(){
System.out.println("前置通知.............");
}
// 2. 环绕通知:@Around(需手动调用proceed()执行目标方法)
@Around(value = "execution(* com.*.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知-前置增强.............");
// 执行目标方法
proceedingJoinPoint.proceed();
System.out.println("环绕通知-后置增强.............");
}
// 3. 最终通知:@After
@After(value = "execution(* com.*.User.add(..))")
public void after() {
System.out.println("最终通知.............");
}
// 4. 异常通知:@AfterThrowing
@AfterThrowing(value = "execution(* com.*.User.add(..))")
public void afterThrowing() {
System.out.println("异常通知.............");
}
// 5. 后置通知:@AfterReturning
@AfterReturning(value = "execution(* com.*.User.add(..))")
public void afterReturning() {
System.out.println("后置通知.............");
}
}</pre></div>
<p class="maodian"></p><h4>步骤5:编写测试类验证</h4>
<p>测试类与配置文件方式一致,直接调用目标方法即可:</p>
<div class="jb51code"><pre class="brush:java;">public class DemoTest {
@Test
public void aopTest1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
user.add();
}
}</pre></div>
<p>运行测试方法,控制台会按照通知的执行顺序输出对应的增强逻辑,说明注解方式的AOP配置生效。</p>
<p class="maodian"></p><h2>五、总结:两种AOP实现方式对比与适用场景</h2>
<p>通过本文的学习,我们掌握了Spring AOP的核心原理和两种实现方式,这里对两种方式进行对比,帮助你选择合适的开发方案:</p>
<ul><li>配置文件方式:配置项清晰,易于理解和维护,适合简单项目或对注解不熟悉的开发者;缺点是配置繁琐,复杂项目中配置文件会过于庞大。</li><li>注解方式:配置简洁高效,减少了配置文件的冗余,适合复杂项目;缺点是注解分散在代码中,对于不熟悉项目结构的开发者来说,定位切面逻辑可能需要花费更多时间。</li></ul>
<p>无论选择哪种方式,核心都是理解AOP的「横向抽取、解耦增强」思想。掌握AOP后,你可以轻松处理日志记录、权限校验、事务管理等通用功能,大幅提升代码质量和开发效率。</p>
<p>最后,建议大家多动手实践,通过修改切入点表达式、切换通知类型等方式,深入理解AOP的执行逻辑,真正将知识转化为实战能力。</p>
<p>到此这篇关于Spring AOP的核心原理和两种实现方式全解析的文章就介绍到这了,更多相关Spring AOP原理内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Spring AOP概念及原理解析</li><li>Spring中的AOP原理与使用详解</li><li>spring动态注册bean AOP失效原理解析</li><li>详解Spring AOP的原理与实现方式</li><li>Spring AOP原理及动态代理</li><li>Spring AOP底层原理及代理模式</li><li>SpringAop实现原理及代理模式详解</li><li>spring aop底层原理及如何实现</li><li>Spring AOP面向切面编程实现原理方法详解</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]