业务方法为什么总被同一圈代码包住
创建订单通常需要开启事务、记录耗时、检查权限:
void createOrder(Order order) {
transaction.begin();
try {
audit.start("create-order");
checkPermission();
saveOrder(order);
transaction.commit();
} catch (Exception error) {
transaction.rollback();
throw error;
}
}这些代码很重要,却不是“创建订单”本身。它们还会重复出现在支付、退款和库存扣减方法中。
把调用变成一条代理链
Spring 可以让调用先经过代理,再进入目标对象:
查看 Mermaid 源码
flowchart LR
caller[调用方] --> tx[事务代理]
tx --> auth[权限代理]
auth --> target[OrderService 目标对象]
target --> store[OrderRepository]调用方拿到的引用可能是代理对象,而不是原始的 OrderService。代理在方法前后执行额外逻辑,目标对象仍然只负责业务。
@Service
class OrderService {
@Transactional
public void createOrder(Order order) {
saveOrder(order);
}
}@Transactional 不是把事务代码插入方法体,而是让容器在创建 Bean 时为它配置事务拦截器。调用代理方法时,拦截器负责开始、提交或回滚事务。
这就是面向切面编程(Aspect-Oriented Programming,AOP)的基本模型:把跨越多个业务模块的横切关注点放到独立切面中,在统一的连接点执行。
声明式编程改变了什么
命令式写法描述步骤:
开始事务 → 执行业务 → 提交事务 → 出错回滚声明式写法描述约束:
这个方法需要事务框架根据声明选择执行策略。事务、缓存和安全都可以采用同样的方式,但它们仍然有自己的失败语义,不能因为写了一行注解就认为操作一定安全。
代理不是魔法,有明确边界
最常见的边界是同类内部调用:
class OrderService {
public void createOrder(Order order) {
save(order); // 直接调用同一个对象的方法
}
@Transactional
public void save(Order order) {
// 可能绕过代理
}
}如果 createOrder 通过 this.save(...) 调用,调用没有经过外层代理,事务拦截器可能不会执行。把需要独立切面的职责拆到另一个 Bean,通常比强行绕过代理更清楚:
@Service
class OrderWriter {
@Transactional
public void save(Order order) {
// 经过代理调用
}
}还要注意:代理只能拦截它能观察到的调用。对象被直接 new 出来、方法是 private、或者调用发生在代理之外时,声明式能力都可能失效。
什么时候不该使用 AOP
如果逻辑只服务一个方法,而且直接写出来更容易读,就不必为了“统一”抽成切面。AOP 适合稳定、横向、可独立验证的规则;复杂业务分支放进切面,会让控制流离开调用点,增加排查成本。
下一篇会换一个视角:除了在调用前后增强方法,Spring 还如何把固定流程封装起来,只让业务代码实现少量变化部分?