Java-Spring入门指南(十二)SpringAOP的三种实现方式
- 前言
- 一、AOP是什么,有什么用,作用是什么?
- 二、AOP的三种实现方式
-
- [方式一:基于Spring Advice接口实现](#方式一:基于Spring Advice接口实现)
- 方式二:自定义切面实现(不依赖Spring接口)
-
- [1. 项目目录结构](#1. 项目目录结构)
- [2. 核心代码实现](#2. 核心代码实现)
- [3. 测试代码与结果](#3. 测试代码与结果)
- 方式三:注解驱动实现(@Aspect注解)
-
- [1. 项目目录结构](#1. 项目目录结构)
- [2. 核心代码实现](#2. 核心代码实现)
- [3. 测试代码与结果](#3. 测试代码与结果)
- 三种实现方式对比
前言
-
在前一篇博客中,我们已经掌握了代理模式的核心逻辑,并初步认识了Spring AOP的概念------通过"切面"为业务方法动态添加增强功能,解决横切关注点(如日志、事务)与核心业务的耦合问题。
-
而在实际开发中,Spring AOP提供了多种灵活的实现方式,适配不同的场景需求。
-
本篇将聚焦Spring AOP的三种核心实现方式,从基于接口的通知实现、自定义切面实现,到注解驱动实现,逐步拆解代码逻辑,帮助你掌握不同方式的配置细节与适用场景,真正将AOP落地到项目开发中。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482

Spring的官方AOP讲解网站
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
一、AOP是什么,有什么用,作用是什么?
在正式讲解实现方式前,我们先快速回顾上一篇的核心内容。
-
AOP是什么 :全称
Aspect Oriented Programming
(面向切面编程),是一种编程思想。它将分散在多个业务方法中的"通用逻辑"(如日志、权限校验)抽取为"切面(Aspect)",在指定时机(如方法执行前/后)动态"织入"到目标方法中,无需修改业务代码。 -
AOP有什么用 :解决"横切关注点"问题。横切关注点是指与核心业务无关,但需重复出现在多个方法中的逻辑(如记录每个业务方法的执行日志)。AOP让这些逻辑只需编写一次,即可作用于多个方法,减少代码冗余、降低耦合、便于统一维护。
-
AOP的核心作用场景:
- 日志记录:自动记录方法调用信息(参数、返回值、执行时间);
- 事务管理:方法执行前开启事务,执行成功提交、失败回滚;
- 权限校验:方法执行前校验用户是否有权限操作;
- 异常处理:统一捕获方法执行中的异常并处理。
-
AOP核心组件回顾:
- 切面(Aspect):封装横切逻辑的类(如"日志切面");
- 通知(Advice):切面中的具体增强逻辑(如"方法执行前打印日志");
- 切入点(Pointcut):通过表达式指定"哪些方法需要被增强";
- 织入(Weaving):将通知动态植入目标方法的过程(Spring在运行时完成)。
二、AOP的三种实现方式
Spring AOP的实现方式围绕"如何定义切面与通知"展开,核心分为基于Spring接口的实现、自定义切面实现、注解驱动实现三种。
使用Spring AOP需添加
aspectjweaver
依赖:
xml
<!-- AspectJ织入依赖:支持AOP功能的核心依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
方式一:基于Spring Advice接口实现
这种方式需让"切面类"实现Spring提供的Advice
系列接口(如MethodBeforeAdvice
前置通知、AfterReturningAdvice
后置通知),通过接口方法定义增强逻辑。Spring会自动识别实现类为"通知类",再通过XML配置绑定"通知"与"切入点"。
1. 项目目录结构
先明确代码组织,将该方式的类放在com.niit.aop1
包下:
spring_aop
└── src
└── main
├── java
│ └── com.niit
│ └── aop1 # 方式一的代码包
│ ├── LogBefore.java # 切面类(实现MethodBeforeAdvice)
│ ├── StudentService.java # 业务接口
│ └── StudentServiceImpl.java # 业务实现类
└── resources
└── applicationContext.xml # Spring核心配置文件
2. 核心代码实现
(1)业务接口与实现类
首先定义业务逻辑(学生管理的增删改查),这是需要被AOP增强的"目标对象":
- StudentService.java(业务接口)
java
// 学生业务接口:定义核心业务方法
public interface StudentService {
void add(); // 添加学生
void del(); // 删除学生
void update(); // 修改学生
void query(); // 查询学生
}
- StudentServiceImpl.java(业务实现类)
java
import org.springframework.stereotype.Component;
// 注册为Spring Bean,id为"ssi"(便于后续从容器中获取)
@Component("ssi")
public class StudentServiceImpl implements StudentService {
// 核心业务逻辑:仅关注"做什么",不关心日志等增强逻辑
@Override
public void add() {
System.out.println("【核心业务】添加学生");
}
@Override
public void del() {
System.out.println("【核心业务】删除学生");
}
@Override
public void update() {
System.out.println("【核心业务】修改学生");
}
@Override
public void query() {
System.out.println("【核心业务】查询学生");
}
}
(2)切面类(实现Advice接口)
创建LogBefore
类,实现MethodBeforeAdvice
接口(Spring提供的"前置通知"接口),在before
方法中定义"方法执行前"的增强逻辑(打印日志):
- LogBefore.java(切面类)
java
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
// 注册为Spring Bean(让Spring管理切面类)
@Component
// 实现MethodBeforeAdvice:代表"目标方法执行前"的增强
public class LogBefore implements MethodBeforeAdvice {
/**
* 前置通知的核心逻辑:目标方法执行前会自动调用此方法
* @param method 被增强的目标方法(如add()、del())
* @param args 目标方法的参数(本例中无参数,为null)
* @param target 被增强的目标对象(如StudentServiceImpl实例)
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// 增强逻辑:打印"哪个类的哪个方法即将执行"
System.out.println("【AOP前置增强】" +
"类:" + target.getClass().getName() +
",方法:" + method.getName() + " 即将执行");
}
}
(3)Spring配置文件(applicationContext.xml)
通过XML配置"切入点"(指定增强哪些方法)和"通知绑定"(将LogBefore的增强逻辑织入切入点):
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- 1. 组件扫描:扫描com.niit包下的@Component注解,注册Bean -->
<context:component-scan base-package="com.niit"/>
<!-- 2. AOP核心配置:方式一(基于Advice接口) -->
<aop:config proxy-target-class="false">
<aop:pointcut id="pointcut" expression="execution(* com.niit.aop1.*.add())"/>
<aop:advisor advice-ref="logBefore" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
- 核心代码解析
1. 根标签 aop:config
- 作用:是Spring AOP XML配置的根标签,所有AOP相关的配置(切入点、通知、切面关联等)都需要定义在该标签内部。
- 属性
proxy-target-class
:
用于指定Spring AOP生成代理的方式:proxy-target-class="false"
(默认值):使用JDK动态代理,要求目标类必须实现接口,代理对象是接口的实现类。proxy-target-class="true"
:使用CGLIB代理,可以代理没有实现接口的类(通过继承目标类生成代理子类)。
2. 切入点配置 aop:pointcut
- 作用:定义"切入点"(Pointcut),即AOP要拦截的方法(哪些方法需要被增强)。
- 属性详解 :
id="pointcut"
:给切入点起一个唯一标识,方便后续引用(如在<aop:advisor>
中关联)。expression
:切入点表达式,用于精确匹配需要拦截的方法,核心语法是execution()
(最常用的表达式类型)。
切入点表达式 execution(* com.niit.aop1.*.add()) 解析
execution()
用于匹配方法执行的连接点,语法结构为:
execution(修饰符 返回值类型 包名.类名.方法名(参数列表))
*
:第一个*
表示"任意返回值类型"(如void、int、Object等)。com.niit.aop1.*
:com.niit.aop1
是包名,后面的*
表示"该包下的所有类"。.add()
:表示匹配类中的add()
方法,且无参数 (如果有参数,需写成add(参数类型)
,如add(int)
)。
整体含义:拦截com.niit.aop1
包下所有类中的add()
无参方法。
3. 切面关联 aop:advisor
-
作用 :将"通知(Advice)"和"切入点(Pointcut)"关联起来,形成一个完整的"切面(Aspect)"。
(这里的
advisor
是Spring AOP中基于Advice
接口的特殊切面形式,一个advisor
只能关联一个通知和一个切入点) -
属性详解:
advice-ref="logBefore"
:引用一个"通知Bean"(id为logBefore
),该Bean需实现Spring的Advice
接口(如MethodBeforeAdvice
前置通知、AfterReturningAdvice
后置通知等)。pointcut-ref="pointcut"
:引用前面定义的切入点(id为pointcut
),表示"通知"要作用在这个切入点匹配的方法上。
核心知识点总结
-
AOP核心概念:
- 切入点(Pointcut):要拦截的方法(通过表达式定义)。
- 通知(Advice):拦截方法后要执行的逻辑(如日志、事务、权限校验等),需实现
Advice
接口。 - 切面(Aspect):切入点 + 通知的组合(这里通过
<aop:advisor>
实现)。
-
基于Advice接口的特点 :
这是Spring早期的AOP配置方式,通知必须严格实现Spring提供的
Advice
相关接口(如MethodBeforeAdvice
、AfterReturningAdvice
),灵活性较低。现在更多使用基于AspectJ的注解方式(如@Before
、@After
)。 -
代理方式选择:
- JDK代理(默认):依赖接口,性能较好。
- CGLIB代理:不依赖接口,可代理任意类,但会生成子类,性能略低。
3. 测试代码与结果
编写Junit测试类,从Spring容器中获取业务Bean,调用方法验证AOP是否生效:
- 测试类代码
java
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void test1() {
// 1. 加载Spring配置文件,初始化容器
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 获取业务Bean:注意!若proxy-target-class=false(JDK代理),需按接口获取
StudentService studentService = (StudentService) ac.getBean("ssi");
// 3. 调用方法:验证增强效果
System.out.println("-----调用add()方法(会被增强)-----");
studentService.add(); // add()在切入点中,会触发前置通知
System.out.println("-----调用del()方法(不被增强)-----");
studentService.del(); // del()不在切入点中,无增强逻辑
System.out.println("-----调用query()方法(不被增强)-----");
studentService.query(); // query()不在切入点中,无增强逻辑
// 4. 关闭容器
ac.close();
}
}
- 测试结果

结果分析 :仅add()
方法触发了前置增强(打印日志),del()
和query()
未被增强,符合切入点表达式的配置,说明AOP生效。
方式一总结
- 核心逻辑 :切面类实现Spring的
Advice
接口,通过<aop:advisor>
绑定通知与切入点; - 代理方式 :支持JDK(基于接口)和CGLIB(基于子类),由
proxy-target-class
控制; - 优缺点:实现简单,适合简单的通知场景;但需依赖Spring接口,灵活性较低(若需多个通知,需实现多个接口)。
方式二:自定义切面实现(不依赖Spring接口)
这种方式无需实现Spring的Advice
接口,而是自定义切面类(包含before、after等增强方法),再通过XML配置将自定义方法指定为"通知",灵活性更高。
1. 项目目录结构
将该方式的类放在com.niit.aop2
包下,与方式一隔离:
spring_aop
└── src
└── main
├── java
│ └── com.niit
│ └── aop2 # 方式二的代码包
│ ├── DiyPointCut.java # 自定义切面类(含增强方法)
│ ├── StudentService.java # 业务接口(与方式一一致)
│ └── StudentServiceImpl.java # 业务实现类
└── resources
└── applicationContext.xml # Spring配置文件
2. 核心代码实现
(1)业务接口与实现类
与方式一完全一致,仅Bean id改为"ssi2"(避免与方式一的Bean重名):
- StudentServiceImpl.java
java
package com.niit.aop2;
import org.springframework.stereotype.Component;
// Bean id改为"ssi2",与方式一的"ssi"区分
@Component("ssi2")
public class StudentServiceImpl implements StudentService {
@Override
public void add() {
System.out.println("【核心业务】添加学生");
}
@Override
public void del() {
System.out.println("【核心业务】删除学生");
}
@Override
public void update() {
System.out.println("【核心业务】修改学生");
}
@Override
public void query() {
System.out.println("【核心业务】查询学生");
}
}
(2)自定义切面类(DiyPointCut)
自定义类,直接编写before
(前置增强)、after
(后置增强)方法,无需实现任何接口:
- DiyPointCut.java
java
import org.springframework.stereotype.Component;
// 注册为Spring Bean
@Component
// 自定义切面类:包含多个增强方法(before、after)
public class DiyPointCut {
// 前置增强方法:目标方法执行前调用
public void before() {
System.out.println("【自定义前置增强】方法即将执行,准备参数校验...");
}
// 后置增强方法:目标方法执行后调用(无论是否抛出异常都会执行)
public void after() {
System.out.println("【自定义后置增强】方法执行完成,记录操作日志...");
}
// 环绕增强方法(可选):包裹目标方法,可在执行前后添加逻辑
public void around() {
System.out.println("【自定义环绕增强】方法执行前的准备工作...");
// 注意:环绕增强若需执行目标方法,需配合ProceedingJoinPoint(后续方式三会演示)
System.out.println("【自定义环绕增强】方法执行后的清理工作...");
}
}

(3)Spring配置文件(applicationContext.xml)
重点使用<aop:aspect>
标签配置自定义切面,将切面中的方法绑定为"通知":
xml
<!-- 继续在applicationContext.xml中添加方式二的配置 -->
<!-- 方式二-->
<aop:config proxy-target-class="true"><!-- 这里用CGLIB代理(true),可直接按类获取Bean,false默认接口 -->
<aop:aspect ref="diyPointCut"> <!-- 1. 定义切面:ref指向自定义切面类(DiyPointCut的Bean id)diyPointCut是DiyPointCut类的默认Bean id(首字母小写) -->
<!-- 2. 定义切入点:增强com.niit.aop2包下的del()方法 -->
<aop:pointcut id="mydiyPointCut" expression="execution(* com.niit.aop2.*.del())"/>
<aop:after method="after" pointcut-ref="mydiyPointCut"></aop:after>
<aop:before method="before" pointcut-ref="mydiyPointCut"></aop:before> <!-- 3. 绑定增强方法与切入点:指定切面中的方法作为通知 -->
</aop:aspect>
</aop:config>

3. 测试代码与结果
- 测试类代码
java
@Test
public void test2() {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 因proxy-target-class=true(CGLIB代理),可直接按类获取Bean
StudentServiceImpl studentService = ac.getBean(StudentServiceImpl.class);
System.out.println("-----调用del()方法(会被增强)-----");
studentService.del(); // del()在切入点中,触发前置+后置增强
System.out.println("-----调用add()方法(不被增强)-----");
studentService.add(); // add()不在切入点中,无增强
ac.close();
}
- 测试结果

方式三:注解驱动实现(@Aspect注解)
这是实际开发中最常用的方式------通过@Aspect
、@Before
、@After
等注解直接在切面类中定义"切面"和"通知",无需复杂的XML配置,仅需开启注解驱动即可。
1. 项目目录结构
将该方式的类放在com.niit.aop3
包下:
spring_aop
└── src
└── main
├── java
│ └── com.niit
│ └── aop3 # 方式三的代码包
│ ├── AnnotationPointCut.java # 注解式切面类
│ ├── StudentService.java # 业务接口
│ └── StudentServiceImpl.java # 业务实现类
└── resources
└── applicationContext.xml # Spring配置文件
2. 核心代码实现
(1)业务接口与实现类
Bean id改为"ssi3",避免重名:
- StudentServiceImpl.java
java
package com.niit.aop3;
import org.springframework.stereotype.Component;
@Component("ssi3")
public class StudentServiceImpl implements StudentService {
@Override
public void add() {
System.out.println("【核心业务】添加学生");
}
@Override
public void del() {
System.out.println("【核心业务】删除学生");
}
@Override
public void update() {
System.out.println("【核心业务】修改学生");
}
@Override
public void query() {
System.out.println("【核心业务】查询学生");
}
}
(2)注解式切面类(AnnotationPointCut)
使用@Aspect
声明切面,@Before
、@After
、@Around
等注解定义通知,execution
表达式直接写在注解中:
- AnnotationPointCut.java
java
package com.niit.aop3;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 1. 注册为Spring Bean
@Component
// 2. @Aspect:声明当前类是"切面类"
@Aspect
public class AnnotationPointCut {
// 3. @Before:前置通知,增强com.niit.aop3包下的add()方法
@Before(value = "execution(* com.niit.aop3.*.add())")
public void before() {
System.out.println("【注解前置通知】add()方法即将执行");
}
// 4. @After:后置通知(目标方法执行后,无论是否异常)
@After(value = "execution(* com.niit.aop3.*.add())")
public void after() {
System.out.println("【注解后置通知】add()方法执行完成");
}
// 5. @Around:环绕通知(最灵活,包裹目标方法,需手动执行目标方法)
@Around(value = "execution(* com.niit.aop3.*.add())")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("【注解环绕通知】方法执行前:开启事务");
// 关键:调用point.proceed()执行目标方法(add())
point.proceed();
System.out.println("【注解环绕通知】方法执行后:提交事务");
}
// 6. @AfterReturning:返回通知(目标方法正常执行完成后触发)
@AfterReturning(value = "execution(* com.niit.aop3.*.add())")
public void afterReturning() {
System.out.println("【注解返回通知】add()方法正常返回,无异常");
}
}
关键说明:
@Around
通知必须传入ProceedingJoinPoint
参数,通过point.proceed()
手动触发目标方法执行;- 通知执行顺序:环绕前 → 前置 → 目标方法 → 后置 → 环绕后 → 返回通知(若无异常)。
(3)Spring配置文件(applicationContext.xml)
无需复杂的<aop:config>
配置,仅需开启注解驱动的AOP即可:
xml
<!-- 方式三:注解驱动AOP -->
<!-- 1. 组件扫描:扫描com.niit包下的@Component和@Aspect注解 -->
<context:component-scan base-package="com.niit"/>
<!-- 2. 开启AspectJ注解驱动:让Spring识别@Aspect、@Before等注解 -->
<aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 可选,默认false(JDK代理),true(CGLIB代理) -->

3. 测试代码与结果
- 测试类代码
java
import com.niit.aop3.StudentService;
@Test
public void test3() {
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = (StudentService) ac.getBean("ssi3");
System.out.println("-----调用add()方法(触发所有注解通知)-----");
studentService.add();
ac.close();
}
- 测试结果

三种实现方式对比
为了更清晰地选择合适的方式,我们通过表格总结核心差异:
实现方式 | 依赖接口 | 配置方式 | 灵活性 | 推荐度 | 适用场景 |
---|---|---|---|---|---|
基于Spring Advice接口 | 需实现Advice系列接口 | XML配置 | 低 | ★★☆☆☆ | 简单通知场景(仅需单一通知) |
自定义切面实现 | 无需接口 | XML配置 | 中 | ★★★☆☆ | 需多个通知,但不想用注解 |
注解驱动实现(@Aspect) | 无需接口 | 注解+少量XML | 高 | ★★★★★ | 企业级开发(推荐首选) |
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482
|--------------------|
| 非常感谢您的阅读,喜欢的话记得三连哦 |
