Spring XML AOP配置实战指南

🔍 总体概览

Spring 提供了两种方式定义 AOP 切面:

  1. @AspectJ 注解风格 :使用 Java 类 + 注解(如 @Aspect, @Before, @After 等)
  2. Schema-based(基于 XML 的 aop 命名空间) :通过 <aop:xxx> 标签在 XML 中配置切面逻辑

本文讲的是第二种------用 XML 写 AOP

虽然现在主流开发更倾向于注解+Java配置,但了解 XML 方式有助于:

  • 维护老项目
  • 深入理解 Spring AOP 底层机制
  • 在某些场景下更灵活控制(比如动态修改配置文件)

🧱 5.5 Schema-based AOP 支持

✅ 前提条件

要使用 <aop:config> 这些标签,必须先引入 Spring AOP 的命名空间(schema)

xml 复制代码
<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.xsd
        http://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 配置 = 切面

示例代码

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 中复用)

xml 复制代码
<aop:config>
    <aop:pointcut id="businessService"
                  expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
  • id="businessService":给 pointcut 起名字,后面可以引用。
  • expression:标准的 AspectJ 切入点表达式,表示匹配 service 包下的所有公共方法。

② 在某个 aspect 内部定义

xml 复制代码
<aop:aspect id="myAspect" ref="aBean">
    <aop:pointcut id="serviceMethod"
                  expression="execution(* com.xyz.service.*.*(..))"/>
</aop:aspect>

③ 使用已有的 @Aspect 类中的命名切入点

如果你已经有类似这样的类:

java 复制代码
@Aspect
public class CommonPointcuts {
    @Pointcut("execution(* com.xyz.service.*.*(..))")
    public void businessService() {}
}

那么可以在 XML 中引用它:

xml 复制代码
<aop:pointcut id="myPC"
              expression="com.xyz.CommonPointcuts.businessService()"/>

⚠️ 注意限制

XML 定义的 pointcut 不能作为命名切入点用于组合新 pointcut(不像 @AspectJ 那样支持嵌套组合)。所以它的复用能力较弱。

💡 绑定 JoinPoint 上下文(如 this, target, args)

你可以从连接点提取参数传入 advice 方法:

xml 复制代码
<aop:pointcut id="serviceWithThis"
              expression="execution(* com.xyz.service.*.*(..)) and this(service)"/>
<aop:before method="monitor" pointcut-ref="serviceWithThis"/>

对应的方法:

java 复制代码
public void monitor(Object service) { ... }

👉 this(service) 把代理对象绑定到变量 service,并自动传递给 advice 方法。

✅ XML 特殊字符处理

因为 && 在 XML 中会报错,可以用单词代替:

原符号 XML 替代
&& and
`
! not

推荐写法:

xml 复制代码
expression="execution(* com.xyz.service.*.*(..)) and this(service)"

📢 5.5.3 声明 Advice(通知)

共有五种通知类型,与 @AspectJ 完全一致:

1. 前置通知(Before Advice)

xml 复制代码
<aop:before 
    pointcut-ref="businessService" 
    method="doAccessCheck"/>
  • 在目标方法执行前运行。
  • method="doAccessCheck" 指向 aspect bean 中的方法。

也可以内联 pointcut:

xml 复制代码
<aop:before 
    pointcut="execution(* com.xyz.dao.*.*(..))"
    method="doAccessCheck"/>

2. 后置返回通知(After Returning)

正常返回后执行:

xml 复制代码
<aop:after-returning 
    pointcut-ref="dataAccessOperation" 
    returning="retVal" 
    method="doLogResult"/>

对应方法需接收 retVal 参数:

java 复制代码
public void doLogResult(Object retVal) { ... }

⚠️ 如果指定了 returning,只有当返回值类型匹配时才会触发。


3. 异常通知(After Throwing)

抛出异常后执行:

xml 复制代码
<aop:after-throwing 
    pointcut-ref="dataAccessOperation" 
    throwing="ex" 
    method="doRecoveryActions"/>

方法签名:

java 复制代码
public void doRecoveryActions(DataAccessException ex) { ... }

只对特定异常类型生效。


4. 最终通知(After Finally)

无论成功或失败都执行(类似 try-finally):

xml 复制代码
<aop:after 
    pointcut-ref="dataAccessOperation" 
    method="doReleaseLock"/>

常用于释放资源、解锁等操作。


5. 环绕通知(Around Advice)

最强大的通知类型,可控制是否继续执行原方法:

xml 复制代码
<aop:around 
    pointcut-ref="businessService" 
    method="doBasicProfiling"/>

实现方法:

java 复制代码
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 配置

xml 复制代码
<aop:aspect id="usageTracker" ref="usageTracking">
    <aop:declare-parents
        types-matching="com.xyz.myapp.service.*+"
        implement-interface="com.xyz.service.tracking.UsageTracked"
        default-impl="com.xyz.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="execution(* com.xyz.myapp.service.*.*(..)) and this(usageTracked)"
        method="recordUsage"/>
</aop:aspect>
  • types-matching:哪些类要被增强?
  • implement-interface:新增实现哪个接口?
  • default-impl:该接口的默认实现类。

之后就可以把任意 service 强转为 UsageTracked

java 复制代码
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。

适合简单场景,比如事务管理。

示例:结合事务管理器使用

xml 复制代码
<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:编写切面类(无注解)

java 复制代码
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 配置

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

🔐 更精细控制:仅对幂等方法重试

定义一个注解:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {}

标记幂等方法:

java 复制代码
@Service
public class OrderService {
    @Idempotent
    public void cancelOrder(String id) { ... }
}

修改 pointcut:

xml 复制代码
<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 项目结构 + 示例代码 👇

是否需要?

相关推荐
桦说编程3 小时前
深入解析CompletableFuture源码实现(3)———多源输入
java·性能优化·源码阅读
xiaozaq3 小时前
java 正则表达式 所有的优先级
java·开发语言·正则表达式
风一样的美狼子3 小时前
仓颉语言核心数据结构-高性能与类型安全的工程实践
java·服务器·前端
جيون داد ناالام ميづ4 小时前
Spring事务原理探索
java·后端·spring
Python私教4 小时前
深入理解 Java 分支语句:从基础到最佳实践
java·后端
艾菜籽4 小时前
MyBatis动态sql与留言墙联系
java·数据库·sql·spring·mybatis
初级程序员Kyle4 小时前
开始改变第五天 Java并发(1)
java
yinke小琪4 小时前
面试官:如何决定使用 HashMap 还是 TreeMap?
java·后端·面试
若疆赤云online4 小时前
SpringGateway处理跨域
java·开发语言