Spring XML AOP配置实战指南
🔍 总体概览
Spring 提供了两种方式定义 AOP 切面:
- @AspectJ 注解风格:使用 Java 类 + 注解(如
@Aspect,@Before,@After等) - Schema-based(基于 XML 的 aop 命名空间):通过
<aop:xxx>标签在 XML 中配置切面逻辑
本文讲的是第二种——用 XML 写 AOP。
虽然现在主流开发更倾向于注解+Java配置,但了解 XML 方式有助于:
- 维护老项目
- 深入理解 Spring AOP 底层机制
- 在某些场景下更灵活控制(比如动态修改配置文件)
🧱 5.5 Schema-based AOP 支持
✅ 前提条件
要使用 <aop:config> 这些标签,必须先引入 Spring AOP 的命名空间(schema)。
<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 https://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
然后才能使用 <aop:config>、<aop:aspect> 等标签。
🏗️ 5.5.1 声明一个 Aspect(切面)
在注解风格里,我们写一个类加上 @Aspect 就是一个切面;
而在 XML 风格中,普通 Java Bean + XML 配置 = 切面
示例代码
<aop:config><aop:aspect id="myAspect" ref="aBean">...</aop:aspect>
</aop:config><bean id="aBean" class="com.example.MyAspectBean"/>
ref="aBean"表示这个切面的行为由MyAspectBean这个 bean 提供。- 所有通知方法(advice methods),比如
doAccessCheck(),都要在这个 bean 里实现。 - 这个 bean 可以像其他任何 Spring Bean 一样被依赖注入!
📌 重点:没有 @Aspect 注解!切面是由 XML 定义的。
🔮 5.5.2 声明 Pointcut(切入点)
Pointcut 是“哪些方法需要被拦截”的规则表达式,语法与 AspectJ 相同。
① 全局定义(可在多个 aspect 中复用)
<aop:config><aop:pointcut id="businessService"expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
id="businessService":给 pointcut 起名字,后面可以引用。expression:标准的 AspectJ 切入点表达式,表示匹配 service 包下的所有公共方法。
② 在某个 aspect 内部定义
<aop:aspect id="myAspect" ref="aBean"><aop:pointcut id="serviceMethod"expression="execution(* com.xyz.service.*.*(..))"/>
</aop:aspect>
③ 使用已有的 @Aspect 类中的命名切入点
如果你已经有类似这样的类:
@Aspect
public class CommonPointcuts {@Pointcut("execution(* com.xyz.service.*.*(..))")public void businessService() {}
}
那么可以在 XML 中引用它:
<aop:pointcut id="myPC"expression="com.xyz.CommonPointcuts.businessService()"/>
⚠️ 注意限制
XML 定义的 pointcut 不能作为命名切入点用于组合新 pointcut(不像 @AspectJ 那样支持嵌套组合)。所以它的复用能力较弱。
💡 绑定 JoinPoint 上下文(如 this, target, args)
你可以从连接点提取参数传入 advice 方法:
<aop:pointcut id="serviceWithThis"expression="execution(* com.xyz.service.*.*(..)) and this(service)"/>
<aop:before method="monitor" pointcut-ref="serviceWithThis"/>
对应的方法:
public void monitor(Object service) { ... }
👉 this(service) 把代理对象绑定到变量 service,并自动传递给 advice 方法。
✅ XML 特殊字符处理
因为 && 在 XML 中会报错,可以用单词代替:
| 原符号 | XML 替代 |
|---|---|
&& | and |
| ` | |
! | not |
推荐写法:
expression="execution(* com.xyz.service.*.*(..)) and this(service)"
📢 5.5.3 声明 Advice(通知)
共有五种通知类型,与 @AspectJ 完全一致:
1. 前置通知(Before Advice)
<aop:before pointcut-ref="businessService" method="doAccessCheck"/>
- 在目标方法执行前运行。
method="doAccessCheck"指向 aspect bean 中的方法。
也可以内联 pointcut:
<aop:before pointcut="execution(* com.xyz.dao.*.*(..))"method="doAccessCheck"/>
2. 后置返回通知(After Returning)
正常返回后执行:
<aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doLogResult"/>
对应方法需接收 retVal 参数:
public void doLogResult(Object retVal) { ... }
⚠️ 如果指定了 returning,只有当返回值类型匹配时才会触发。
3. 异常通知(After Throwing)
抛出异常后执行:
<aop:after-throwing pointcut-ref="dataAccessOperation" throwing="ex" method="doRecoveryActions"/>
方法签名:
public void doRecoveryActions(DataAccessException ex) { ... }
只对特定异常类型生效。
4. 最终通知(After Finally)
无论成功或失败都执行(类似 try-finally):
<aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/>
常用于释放资源、解锁等操作。
5. 环绕通知(Around Advice)
最强大的通知类型,可控制是否继续执行原方法:
<aop:around pointcut-ref="businessService" method="doBasicProfiling"/>
实现方法:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {System.out.println("开始计时");long start = System.currentTimeMillis();Object result = pjp.proceed(); // 执行原方法long elapsed = System.currentTimeMillis() - start;System.out.println("耗时:" + elapsed + "ms");return result;
}
✅ 第一个参数必须是 ProceedingJoinPoint。
🧩 5.5.4 Introductions(引介 / 引入新接口)
类似于“让已有类额外实现某个接口”。
例如:让所有 Service 类都实现 UsageTracked 接口,记录调用次数。
XML 配置
<aop:aspect id="usageTracker" ref="usageTracking"><aop:declare-parentstypes-matching="com.xyz.myapp.service.*+"implement-interface="com.xyz.service.tracking.UsageTracked"default-impl="com.xyz.service.tracking.DefaultUsageTracked"/><aop:beforepointcut="execution(* com.xyz.myapp.service.*.*(..)) and this(usageTracked)"method="recordUsage"/>
</aop:aspect>
types-matching:哪些类要被增强?implement-interface:新增实现哪个接口?default-impl:该接口的默认实现类。
之后就可以把任意 service 强转为 UsageTracked:
UsageTracked tracked = (UsageTracked) context.getBean("userService");
tracked.getUseCount();
🪄 5.5.5 实例化模型(Instantiation Models)
目前只支持 singleton(单例)模式。
也就是说每个 <aop:aspect ref="..."> 对应的 bean 必须是单例的,不支持 per-this 或 per-target 等高级实例化模型。
📝 5.5.6 Advisors(顾问)
Advisor 是 Spring 特有的轻量级“切面”,通常只包含一个 advice 和一个 pointcut。
适合简单场景,比如事务管理。
示例:结合事务管理器使用
<aop:config><aop:pointcut id="businessService"expression="execution(* com.xyz.service.*.*(..))"/><aop:advisor pointcut-ref="businessService" advice-ref="txAdvice"/>
</aop:config><tx:advice id="txAdvice"><tx:attributes><tx:method name="*" propagation="REQUIRED"/></tx:attributes>
</tx:advice>
advice-ref指向一个实现了 Spring Advice 接口的 bean(如 MethodInterceptor)。- 常见用途:事务、缓存、安全等通用横切逻辑。
🛠️ 5.5.7 完整示例:重试机制(并发锁失败自动重试)
场景需求
某些业务方法可能因数据库死锁失败(PessimisticLockingFailureException),但我们希望自动重试几次再抛异常。
这属于典型的横切关注点 → 适合用 AOP 实现。
Step 1:编写切面类(无注解)
public class ConcurrentOperationExecutor implements Ordered {private int maxRetries = 2;private int order = 1;public void setMaxRetries(int maxRetries) {this.maxRetries = maxRetries;}public int getOrder() {return order;}public void setOrder(int order) {this.order = order;}public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {int attempts = 0;PessimisticLockingFailureException lastException;do {attempts++;try {return pjp.proceed(); // 尝试执行} catch (PessimisticLockingFailureException e) {lastException = e;}} while (attempts <= maxRetries);throw lastException; // 重试完仍失败则抛出}
}
Step 2:XML 配置
<aop:config><aop:aspect id="concurrentRetry" ref="concurrentOperationExecutor"><aop:pointcut id="idempotentOp"expression="execution(* com.xyz.service.*.*(..))"/><aop:around pointcut-ref="idempotentOp"method="doConcurrentOperation"/></aop:aspect>
</aop:config><bean id="concurrentOperationExecutor"class="com.xyz.ConcurrentOperationExecutor"><property name="maxRetries" value="3"/><property name="order" value="100"/>
</bean>
✅ 效果
所有 service 方法如果抛出 PessimisticLockingFailureException,会最多重试 3 次。
🔐 更精细控制:仅对幂等方法重试
定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {}
标记幂等方法:
@Service
public class OrderService {@Idempotentpublic void cancelOrder(String id) { ... }
}
修改 pointcut:
<aop:pointcut id="idempotentOp"expression="execution(* com.xyz.service.*.*(..)) and @annotation(com.xyz.Idempotent)"/>
→ 只对加了 @Idempotent 的方法进行重试。
🧠 总结对比:Schema vs @AspectJ
| 功能 | Schema-based (XML) | @AspectJ 注解 |
|---|---|---|
| 定义位置 | XML 文件 | Java 类 |
是否需要 @Aspect | ❌ 不需要 | ✅ 需要 |
| Pointcut 复用性 | 较差(无法嵌套组合) | 强(可用方法调用) |
| 类型安全性 | 弱(字符串表达式) | 强(编译期检查) |
| 参数绑定 | 支持(按名称匹配) | 支持 |
| 调试难度 | 高(分散在 XML 和 Java 中) | 低(集中在一个类) |
| 适用场景 | 老项目、事务配置、集中式策略 | 新项目、复杂切面逻辑 |
✅ 学习建议
- 新手入门:优先学习
@AspectJ风格(更直观)。 - 维护旧系统:必须掌握 schema-based 配置。
- 最佳实践:避免混合使用
<aop:config>和BeanNameAutoProxyCreator,否则可能导致代理失效。 - 性能提示:大量使用 AOP 会影响启动速度和内存占用,合理设计切面粒度。
📚 结语
你看到的这部分文档非常全面,涵盖了 Spring AOP 的 XML 配置方式的所有关键特性。虽然现代开发中较少手写这些 XML,但它背后的原理(pointcut 表达式、advice 执行顺序、around 控制流程、introduction 扩展功能等)在各种框架(如 Spring Security、Spring Retry、Spring Cache)中都有广泛应用。
理解这一章,等于掌握了 Spring AOP 的底层运作机制。
如果你想动手练习,我可以帮你生成完整的 Maven 项目结构 + 示例代码 👇
是否需要?
