Spring AOP 入门教程:基础概念与实现
目录
第一章:AOP概念的引入
第二章:AOP相关的概念
1. AOP概述
2. AOP的优势
3. AOP的底层原理
第三章:Spring的AOP技术 - 配置文件方式
1. AOP相关的术语
2. AOP配置文件方式入门
3. 切入点的表达式
4. AOP的通知类型
第四章:Spring AOP技术 - 注解方式
1. 创建 Maven 工程,导入依赖
2. 编写接口并完成 IOC 配置
3. 编写切面类
4. 配置 XML 扫描注解
5. 配置 XML 自动代理
6. 编写 User 类
7. 通知类型注解
8. 测试类
9. 结果输出
Spring完整基础内容,请看这篇博客 :
Spring基础之——控制反转(IOC)、依赖注入(DI)与切面编程(AOP)概念详解(适合小白,初学者必看)_spring 控制反转、依赖注入、面向切面编程的代码呈现-CSDN博客文章浏览阅读1.1k次,点赞30次,收藏18次。本篇博客讲详细介绍Spring框架中的两个最核心且最基础的概念:控制反转(IOC)和面向切面编程(AOP)。以及如何通过IDEA来构建一个Spring项目,通过实战和理论结合的方式来让大家真的学会Spring这个最流行的Java框架。Spring是一个开放源代码的设计层面框架,它解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式) 轻量级开源框架。它是为了解决企业应用开发的复杂性而创建的。_spring 控制反转、依赖注入、面向切面编程的代码呈现https://blog.csdn.net/Future_yzx/article/details/143406347?sharetype=blogdetail&sharerId=143406347&sharerefer=PC&sharesource=Future_yzx&spm=1011.2480.3001.8118
前言
在本篇博客中,我们将详细介绍 Spring AOP(面向切面编程) 的基本概念、应用及实现方法。AOP 是一种能够在不修改源代码的前提下对程序进行增强的技术,广泛应用于日志记录、性能统计、权限校验等领域。我们将从概念入手,逐步展示如何使用 Spring AOP 实现增强功能。
第一章:AOP概念的引入
首先我们来看一下用户登录的原理流程
我们先从一个简单的登录原理图开始思考,如果我们希望在登录功能中添加新的操作,例如权限校验,通常可以采取两种方式:
- 修改源代码:直接在原有代码中添加权限校验逻辑。
- 不修改源代码:通过 AOP 技术,在不改变现有代码的情况下进行增强。
Spring AOP 技术恰恰解决了第二种需求,它允许我们在现有业务逻辑上加入横向功能(如权限检查、日志记录等),而不需要直接修改源代码。
第二章:AOP相关的概念
1. AOP概述
AOP(Aspect-Oriented Programming)是“面向切面编程”的缩写,它是对传统 OOP(面向对象编程) 的补充,通过在程序执行过程中动态地插入额外的功能来增强代码的可复用性与模块化。
Spring AOP 通过 代理 机制实现切面功能,通常在运行时为目标对象创建代理对象,从而实现方法增强。
2. AOP的优势
AOP 的核心优势在于它能够 解耦 业务逻辑和横向功能(如日志、事务、缓存等),从而提高代码的可维护性、重用性及开发效率。
AOP 的优势总结:
- 减少重复代码:避免在每个业务方法中重复相同的代码。
- 提高开发效率:通过统一处理横向功能,简化业务逻辑。
- 方便维护:可以在不改变核心业务逻辑的前提下,修改或添加横向功能。
3. AOP的底层原理
Spring AOP 的实现基于 动态代理技术,主要有两种方式:
(1)JDK 动态代理:通过接口创建代理类。
- 为接口创建代理类的字节码文件
- 使用ClassLoader将字节码文件加载到JVM
- 创建代理类实例对象,执行对象的目标方法
(2)CGLIB 代理:通过继承目标类生成代理类,适用于没有接口的类。
- 为类生成代理对象,被代理类有没有接口都无所谓,底层是生成子类,继承被代理类
第三章:Spring的AOP技术 - 配置文件方式
1. AOP相关的术语
在 AOP 中,以下是一些关键术语:
- Joinpoint(连接点):表示在程序中可以插入增强的地方,通常是方法。
- Pointcut(切入点):定义了哪些连接点会被拦截。
- Advice(通知):在切入点处执行的代码,分为不同类型(前置通知、后置通知、环绕通知等)。
- Aspect(切面):切入点与通知的结合,表示一个完整的 AOP 增强。
2. AOP配置文件方式入门
首先,我们需要创建一个 Maven 项目并导入以下依赖:
<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>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
</dependencies>
创建被增强的类(目标类)
// 被增强的类
public class User {
// 连接点/切入点
public void add() {
System.out.println("add......");
}
public void update() {
System.out.println("update......");
}
}
创建切面类
public class UserProxy {
// 增强/通知 ---》前置通知
public void before() {
System.out.println("before.............");
}
}
在配置文件中完成aop的配置
<!-- 定义目标类和切面类 -->
<bean id="user" class="com.aopImpl.User"></bean>
<bean id="userProxy" class="com.aopImpl.UserProxy"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切面 = 切入点 + 通知组成 -->
<aop:aspect ref="userProxy">
<!-- 前置通知:UserServiceImpl的save方法执行前,会增强 -->
<aop:before method="before" pointcut="execution(public void com.aopImpl.User.add())"/>
</aop:aspect>
</aop:config>
测试类
public class DemoTest {
@Test
public void aopTest1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
user.add();
}
}
3. 切入点的表达式
再配置切入点的时候,需要定义表达式,具体展开如下:
① 切入点表达式的格式如下:
execution( [修饰符] [返回值类型] [类全路径] [方法名 ( [参数] )] )
注意:
- 修饰符不是必须要出现的,可以省略不写。
- 返回值类型是不能省略不写的,根据你的方法来编写返回值,可以使用 * 代替。
② 包名,类名,方法名,参数的规则如下:
例如:com.qcby.demo3.BookDaoImpl.save()
- 首先包名,类名,方法名不能省略不写,可以使用 * 代替
- 中间的包名可以使用 * 号代替
- 类名也可以使用 * 号代替,例如有类似的写法: *DaoImpl
- 方法也可以使用 * 号代替
- 参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..
比较通用的表达式:execution(* com.qcby.*.ServiceImpl.save(..))
举例2:对 com.qcby.demo3.BookDaoImpl 当中所有的方法进行增强
execution(* com.qcby.*.ServiceImpl.*(..))
举例3:对 com.qcby.demo3 包当中所有的方法进行增强
execution(* com.qcby.*.*.*(..))
代码实例:
<!--配置切面-->
<aop:config>
<!--配置切面 = 切入点 + 通知组成-->
<aop:aspect ref="userProxy">
<!--切入点的表达式
execution() 固定的写法
public 是可以省略不写的
方法的返回值 int String 通用的写法,可以编写 * 不能省略不写的
包名+类名 不能省略不写的,编写 * com.*
方法名称 add() 可以写 *
参数列表 (..) 表示任意类型和个数的参数
比较通用的表达式:execution(* com.*.User.add(..))-->
<aop:before method="before" pointcut="execution(* com.*.User.add(..))"/>
</aop:aspect>
</aop:config>
4. AOP的通知类型
Spring AOP 提供了多种通知类型,以下是常用的通知:
- 前置通知(Before):在目标方法执行前进行增强。
- 环绕通知(Around):在目标方法执行前后都可以增强,且需要手动调用目标方法。
- 最终通知(After):无论目标方法执行成功还是失败,都会执行。
- 后置通知(AfterReturning):目标方法执行成功后执行。
- 异常通知(AfterThrowing):当目标方法抛出异常时执行。
接下来,我们一一展开并配置这些通知。
1. 前置通知(Before)
目标:目标方法执行前进行增强。
适用场景:例如,进行权限检查或日志记录时,在方法执行之前就进行操作。
代码示例:
// 前置通知
public void before() {
System.out.println("before.............");
}
XML 配置:
<aop:before method="before" pointcut="execution(public void com.aopImpl.User.add())"/>
2. 环绕通知(Around)
目标:目标方法执行前后都可以进行增强。需要手动执行目标方法。
适用场景:例如,在方法执行前后做性能监控、事务管理等,或者在执行方法之前和之后加上额外的逻辑。
代码示例:
// 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("before.............");
// 执行被增强的方法
proceedingJoinPoint.proceed();
System.out.println("after.............");
}
XML 配置:
<aop:around method="around" pointcut="execution(* com.*.User.add(..))"/>
3. 最终通知(After)
目标:目标方法执行成功或者失败时都进行增强。
适用场景:例如,无论方法执行是否成功,都需要执行清理操作(如关闭资源)。
代码示例:
// 最终通知
public void after() {
System.out.println("after.............");
}
XML 配置:
<aop:after method="after" pointcut="execution(* com.*.User.add(..))"/>
4. 后置通知(AfterReturning)
目标:目标方法执行成功后进行增强。
适用场景:例如,在方法执行成功后进行记录日志,或者根据方法的返回值进行后续处理。
代码示例:
// 后置通知
public void afterReturning() {
System.out.println("afterReturning.............");
}
XML 配置:
<aop:after-returning method="afterReturning" pointcut="execution(public void com.aopImpl.User.add())"/>
5. 异常通知(AfterThrowing)
目标:目标方法执行失败(抛出异常)时进行增强。
适用场景:例如,当目标方法出现异常时记录日志、发送报警等。
代码示例:
// 异常通知
public void afterThrowing() {
System.out.println("afterThrowing.............");
}
连接点/切入点:
这里的目标方法 add()
在执行时故意抛出异常,用于触发异常通知。
// 连接点/切入点
public void add() {
int a = 10 / 0; // 故意抛出异常
System.out.println("add......");
}
XML 配置:
<aop:after-throwing method="afterThrowing" pointcut="execution(public void com.aopImpl.User.add())"/>
适用场景:
- 前置通知:适用于方法执行前需要进行一些准备工作,如权限验证、输入检查等。
- 环绕通知:适用于你需要控制方法执行的前后逻辑,能够在方法执行前后插入自定义行为。
- 最终通知:适用于在方法执行后无论成功与否都需要执行的操作,如资源清理等。
- 后置通知:适用于方法执行成功后进行的操作,例如记录成功日志或结果处理。
- 异常通知:适用于方法执行时发生异常,需要对异常进行处理,如记录日志或发送告警通知。
第四章:Spring AOP技术 - 注解方式
下面用一个AOP注解方式入门程序来为大家演示:注解方式使用AOP。
1. 创建 Maven 工程,导入依赖
在 pom.xml
中添加必要的 Spring 相关依赖。具体依赖配置略。
2. 编写接口并完成 IOC 配置
(此部分略,假设已经完成)
3. 编写切面类
给切面类添加 @Aspect
注解,编写增强的方法,使用通知类型注解声明:
@Component
@Aspect // 生成代理对象
public class UserProxy {
// 增强/通知 ---》前置通知
@Before(value = "execution(* com.*.User.add(..))")
public void before() {
System.out.println("before.............");
}
// 环绕通知
@Around(value = "execution(* com.*.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("before.............");
// 执行被增强的方法
proceedingJoinPoint.proceed();
System.out.println("after.............");
}
// 最终通知
@After(value = "execution(* com.*.User.add(..))")
public void after() {
System.out.println("after.............");
}
// 异常通知
@AfterThrowing(value = "execution(* com.*.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.............");
}
// 后置通知
@AfterReturning(value = "execution(* com.*.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.............");
}
}
4. 配置 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">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.aopImpl"></context:component-scan>
</beans>
5. 配置 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">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.aopImpl"></context:component-scan>
<!-- 开启 AspectJ 自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
6. 编写 User
类
@Component
public class User {
// 连接点/切入点
public void add() {
System.out.println("add......");
}
}
7. 通知类型注解
@Before
: 前置通知@AfterReturning
: 后置通知@Around
: 环绕通知(目标对象方法默认不执行,需要手动调用)@After
: 最终通知@AfterThrowing
: 异常抛出通知
@Component
@Aspect //生成代理对象
public class UserProxy {
//增强/通知 ---》前置通知
@Before(value = "execution(* com.*.User.add(..))")
public void before(){
System.out.println("before.............");
}
// 环绕通知
@Around(value = "execution(* com.*.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("before.............");
// 执行被增强的方法
proceedingJoinPoint.proceed();
System.out.println("after.............");
}
// 最终通知
@After(value = "execution(* com.*.User.add(..))")
public void after() {
System.out.println("after.............");
}
//异常通知
@AfterThrowing(value = "execution(* com.*.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.............");
}
//后置通知
@AfterReturning(value = "execution(* com.*.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.............");
}
}
8. 测试类
@Test
public void aopTest1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
user.add();
}
9. 结果输出
当 user.add()
被调用时,输出将会是:
before.............
before.............
add......
after.............
after.............
afterReturning.............
这样就完成了一个基本的 AOP 注解方式入门程序,涵盖了前置通知、后置通知、环绕通知、最终通知和异常通知的使用。
如果有更多问题或者需要进一步的讲解,随时告诉我!
结语
在本文中,我们介绍了 Spring AOP 的基本概念、配置文件方式和注解方式的实现。通过 Spring AOP,我们可以在不修改源代码的前提下灵活地为现有代码添加横向功能,提高代码的复用性和可维护性。希望本文能帮助你更好地理解和使用 Spring AOP!