java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring AOP原理

Spring AOP的核心原理和两种实现方式全解析

作者:柒.梧.

本文介绍Spring AOP的核心原理和两种实现方式全解析,通过本文的学习,我们掌握了Spring AOP的核心原理和两种实现方式,这里对两种方式进行对比,帮助你选择合适的开发方案,感兴趣的朋友跟随小编一起看看吧

在Spring框架的学习旅程中,AOP(面向切面编程)绝对是核心重点之一。它打破了传统纵向编程的思维局限,通过横向抽取机制解决了代码冗余、耦合度高的痛点。本文将从AOP的概念引入出发,层层拆解核心原理,再通过完整实战案例覆盖配置文件和注解两种实现方式,帮你彻底掌握Spring AOP的应用精髓。

一、为什么需要AOP?从登录功能增强场景说起

要理解AOP的价值,我们先从一个常见的业务场景切入——登录功能的增强。在基础的登录功能实现中,核心逻辑是校验用户账号密码的合法性,验证通过后即可完成登录流程。

但随着业务迭代,我们往往需要在登录功能之上叠加新的需求,比如「权限校验」:不同角色的用户登录后能访问的资源不同,需要在登录后额外判断角色权限。此时,我们有两种实现思路:

第一种方案看似直接,却存在诸多问题:一旦核心业务逻辑(如登录)需要叠加多个增强功能(权限校验、日志记录、缓存处理等),源代码会变得臃肿不堪,后续维护难度剧增;同时,这些增强功能在多个业务模块中可能重复出现,导致代码冗余。

而第二种方案正是AOP的核心思想——在不修改原有业务代码的前提下,对程序功能进行增强。这种方式能实现业务逻辑与增强逻辑的解耦,让代码结构更清晰、可维护性更强。

二、AOP核心概念深度拆解

2.1 什么是AOP?

AOP全称为Aspect Oriented Programming,即面向切面编程。它并非一种具体的技术,而是一种编程范式,隶属于软件工程范畴,指导开发者如何更合理地组织程序结构。

AOP的思想最早由AOP联盟提出并制定了相关规范,Spring框架引入AOP思想时完全遵循该规范。其核心是通过预编译运行期动态代理技术,实现程序功能的统一维护。

作为OOP(面向对象编程)的延续,AOP弥补了OOP在横向功能扩展上的不足。OOP通过继承和封装实现纵向的功能复用,而AOP通过横向抽取机制,将分散在各个业务模块中的重复代码(如事务管理、安全检查、日志记录、缓存处理等)抽取出来,形成独立的「切面」,再动态植入到需要增强的业务方法中。

一句话总结AOP的价值:隔离业务逻辑与增强逻辑,降低耦合度,提高代码可重用性和开发效率

2.2 AOP的核心优势

基于AOP的设计思想,其核心优势主要体现在三个方面:

2.3 AOP底层实现原理

Spring AOP的底层依赖两种动态代理技术实现,具体使用哪种技术取决于被增强的类是否实现接口:

1. JDK动态代理

当被增强的类实现了接口时,Spring会采用JDK动态代理技术生成代理对象,核心步骤如下:

2. CGLIB动态代理

当被增强的类没有实现任何接口时,Spring会采用CGLIB动态代理技术。其核心原理是生成被代理类的子类作为代理对象,通过重写父类的方法植入增强逻辑。由于是基于继承实现的,被代理类不能是final修饰的(final类无法被继承)。

三、Spring AOP实战:配置文件方式

Spring AOP的实现方式有两种:配置文件方式(XML)和注解方式。我们先从配置文件方式入手,通过完整的实战案例理解AOP的核心术语和实现流程。

3.1 先搞懂AOP的核心术语

在开始实战前,必须先明确AOP的几个核心术语,否则会对配置逻辑感到困惑:

3.2 实战准备:环境搭建

本案例基于Maven构建项目,首先需要引入相关依赖。Spring AOP的实现依赖Spring核心包、AOP联盟规范包、AspectJ相关包(用于解析切入点表达式)等,具体依赖如下:

<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>

3.3 实战步骤:实现方法增强

步骤1:创建被增强的目标类(核心业务类)

创建一个User类,包含add和update两个核心业务方法,这两个方法就是我们潜在的连接点:

// 被增强的目标类(核心业务类)
public class User {
    // 连接点/切入点
    public void add(){
        System.out.println("add......");
    }
    public void update(){
        System.out.println("update......");
    }
}

步骤2:将目标类交给Spring管理

在Spring配置文件(applicationContext.xml)中配置目标类的Bean:

<bean id="user" class="com.aopImpl.User"></bean>

步骤3:创建切面类(增强逻辑载体)

创建UserProxy类作为切面类,在其中定义增强逻辑(通知)。这里先实现一个前置通知(目标方法执行前执行的增强逻辑):

// 切面类(包含增强逻辑)
public class UserProxy {
    // 前置通知:目标方法执行前执行
    public void before(){
        System.out.println("before.............");
    }
}

步骤4:将切面类交给Spring管理

在配置文件中配置切面类的Bean:

<bean id="userProxy" class="com.aopImpl.UserProxy"></bean>

步骤5:配置AOP(绑定切入点和通知)

在配置文件中通过<aop:config>标签完成切面的配置,核心是绑定切入点(要增强的方法)和通知(增强逻辑):

<!--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>

步骤6:编写测试类验证结果

通过Spring容器获取目标类的Bean,调用add方法,观察是否执行增强逻辑:

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();
    }
}

3.4 切入点表达式详解

在AOP配置中,切入点表达式是核心,它的作用是精准定位要增强的方法。切入点表达式的完整格式如下:

execution([修饰符] [返回值类型] [类全路径] [方法名 ( [参数] )])

各部分说明及使用规则:

常用切入点表达式示例:

3.5 五种通知类型的实现

除了前置通知,Spring AOP还支持五种通知类型,分别对应不同的执行时机。下面我们在UserProxy切面类中补充所有通知类型的实现,并完成配置:

1. 前置通知(@Before)

目标方法执行前执行,已在上面的案例中实现,配置方式:

<aop:before method="before" pointcut="execution(* com.*.User.add(..))"/>

2. 环绕通知(@Around)

目标方法执行前后都执行,需要手动调用ProceedingJoinPointproceed()方法触发目标方法执行:

// 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕通知-前置增强.............");
    // 手动执行目标方法
    proceedingJoinPoint.proceed();
    System.out.println("环绕通知-后置增强.............");
}

配置方式: 

<aop:around method="around" pointcut="execution(* com.*.User.add(..))"/>

3. 最终通知(@After)

目标方法无论执行成功还是失败,都会执行(类似try-catch中的finally块):

// 最终通知
public void after() {
    System.out.println("最终通知.............");
}

配置方式:

<aop:after method="after" pointcut="execution(* com.*.User.add(..))"/>

4. 后置通知(@AfterReturning)

目标方法执行成功后才会执行,若目标方法抛出异常则不执行:

// 后置通知
public void afterReturning() {
    System.out.println("后置通知.............");
}

配置方式: 

<aop:after-returning method="afterReturning" pointcut="execution(* com.*.User.add(..))"/>

5. 异常通知(@AfterThrowing)

目标方法执行失败(抛出异常)时才会执行:

// 异常通知
public void afterThrowing() {
    System.out.println("异常通知.............");
}

为了验证异常通知,需要修改目标类的add方法,手动抛出异常:

public void add(){
    // 手动制造异常
    int a = 10 / 0;
    System.out.println("add......");
}

配置方式: 

<aop:after-throwing method="afterThrowing" pointcut="execution(* com.*.User.add(..))"/>

四、Spring AOP实战:注解方式(更简洁高效)

配置文件方式的AOP虽然逻辑清晰,但配置项较多,对于复杂项目会显得繁琐。Spring提供了注解方式的AOP实现,通过注解可以快速完成切面的配置,更符合现代开发习惯。

4.1 注解方式核心步骤

步骤1:环境准备

依赖与配置文件方式一致,无需额外引入依赖。

步骤2:开启注解扫描和AOP自动代理

在Spring配置文件中添加注解扫描和AOP自动代理的配置,开启注解驱动的AOP功能:

<?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>

步骤3:使用注解标识目标类和切面类

通过@Component注解将目标类和切面类交给Spring管理,通过@Aspect注解标识切面类:

// 目标类:通过@Component交给Spring管理
@Component
public class User {
    public void add(){
        System.out.println("add......");
    }
}
// 切面类:@Component交给Spring管理,@Aspect标识为切面类
@Component
@Aspect  
public class UserProxy {
    // 增强逻辑将在下面通过注解配置
}

步骤4:使用注解配置通知和切入点

Spring提供了对应的注解来配置五种通知类型,注解的value属性用于指定切入点表达式:

@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("后置通知.............");
    }
}

步骤5:编写测试类验证

测试类与配置文件方式一致,直接调用目标方法即可:

public class DemoTest {
    @Test
    public void aopTest1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) applicationContext.getBean("user");
        user.add();
    }
}

运行测试方法,控制台会按照通知的执行顺序输出对应的增强逻辑,说明注解方式的AOP配置生效。

五、总结:两种AOP实现方式对比与适用场景

通过本文的学习,我们掌握了Spring AOP的核心原理和两种实现方式,这里对两种方式进行对比,帮助你选择合适的开发方案:

无论选择哪种方式,核心都是理解AOP的「横向抽取、解耦增强」思想。掌握AOP后,你可以轻松处理日志记录、权限校验、事务管理等通用功能,大幅提升代码质量和开发效率。

最后,建议大家多动手实践,通过修改切入点表达式、切换通知类型等方式,深入理解AOP的执行逻辑,真正将知识转化为实战能力。

到此这篇关于Spring AOP的核心原理和两种实现方式全解析的文章就介绍到这了,更多相关Spring AOP原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文