代理模式 vs AOP:支付服务中的日志增强实践(含执行顺序详解)
目录
- 一、实现支付服务
- 1. 定义支付服务接口
- 2. 实现支付服务接口
- 3. 测试支付服务
- 4. 引入代理模式
- 5. 测试代理模式
- 6. 总结
- 二、execution和annotation区别
- 1. execution 表达式
- 2. @annotation 表达式
- 3. 对比
- 三、AOP实现(执行顺序)
- 四、代理模式 vs AOP
一、实现支付服务
在本篇博客中,我们将展示如何使用Java中的接口和代理模式实现一个简单的支付服务系统。我们会先定义一个基础的支付服务接口,并为其提供一个实现类。接着,我们将通过代理模式为支付服务添加额外的行为,例如支付前后的日志输出。
1. 定义支付服务接口
首先,我们定义一个简单的PayService
接口,所有的支付方式都需要实现该接口。接口中包含一个pay()
方法,用于执行支付操作。
public interface PayService {void pay(); // 支付操作
}
2. 实现支付服务接口
接下来,我们创建一个支付服务的实现类PayServiceImpl
,模拟微信支付的支付逻辑。我们将打印出一个模拟的支付交易号,并且暂时注释掉一些错误的代码(如除零错误),避免程序崩溃。
@Service
public class PayServiceImpl implements PayService {@Overridepublic void pay() {System.out.println("微信支付real " + IdUtil.simpleUUID());// int age = 10 / 0; // 模拟错误,如果需要,可以开启这一行来测试异常}
}
@Service
注解表明PayServiceImpl
是一个Spring的服务类,Spring会管理它的生命周期。IdUtil.simpleUUID()
用于生成一个唯一的UUID,模拟支付交易号。
3. 测试支付服务
在这里,我们简单测试一下PayServiceImpl
的实现:
public class ClientTest {private static void payNormalV1() {PayService payService = new PayServiceImpl();payService.pay(); // 执行支付}public static void main(String[] args) {payNormalV1(); // 调用普通支付}
}
执行时,控制台会输出类似以下内容:
4. 引入代理模式
在实际开发中,我们经常使用代理模式来增强现有功能,比如增加日志记录、权限检查、事务管理等。在本例中,我们将使用代理模式包装PayService
,并在支付前后增加一些自定义逻辑(例如打印日志)。
public class PayProxy implements PayService {private PayService payService; // 目标对象// 构造注入public PayProxy(PayService payService) {this.payService = payService;}// 支付前的准备工作private void before() {System.out.println("-----before,打开微信支付");}@Overridepublic void pay() {before(); // 执行支付前的操作payService.pay(); // 委托目标对象执行实际的支付after(); // 执行支付后的操作}// 支付后的操作private void after() {System.out.println("-----after,关闭微信支付");}
}
在PayProxy
类中,我们通过构造器注入了一个PayService
实例。pay()
方法首先调用before()
方法打印日志,然后调用目标对象(即PayServiceImpl
)的pay()
方法,最后执行after()
方法,输出支付后的日志。
5. 测试代理模式
我们将创建一个新的测试方法来演示代理模式的使用:
public class ClientTest {private static void payNormalV1() {PayService payService = new PayServiceImpl();payService.pay(); // 执行普通支付}private static void payProxyV2() {PayService payService = new PayProxy(new PayServiceImpl());payService.pay(); // 执行代理支付}public static void main(String[] args) {payNormalV1(); // 调用普通支付System.out.println("=====================================");payProxyV2(); // 调用代理支付}
}
当执行payNormalV1()
时,控制台会输出普通的支付信息;而执行payProxyV2()
时,代理模式会在支付前后打印出更多的日志。
输出结果如下:
6. 总结
- 我们首先定义了
PayService
接口和PayServiceImpl
实现类,用于模拟支付操作。 - 然后我们使用代理模式
PayProxy
来包装PayServiceImpl
,为支付操作添加了额外的行为(支付前后的日志输出)。 - 最后,我们通过测试类
ClientTest
展示了如何分别使用普通支付和代理支付。
这种代理模式可以在不修改原始支付逻辑的情况下,添加更多的功能,例如性能监控、日志记录、事务管理等。
二、execution和annotation区别
1. execution 表达式
-
作用:根据方法的签名(访问修饰符、返回值、包名、类名、方法名、参数)来定义切点。
-
特点:语法比较强大,可以非常精准地匹配方法。
-
示例:
@Before("execution(* com.example.service.*.*(..))") public void beforeExecution() {System.out.println("通过 execution 拦截 service 包下的所有方法"); }
2. @annotation 表达式
-
作用:根据方法上是否存在某个注解来匹配切点。
-
特点:更灵活,尤其适合只想增强带某个自定义注解的方法。
-
示例:
// 定义一个自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PayLog { }
@Aspect @Component public class PayAspect {// 匹配所有带 @PayLog 注解的方法@Before("@annotation(com.example.annotation.PayLog)")public void beforeAnnotation() {System.out.println("通过 @annotation 拦截带 @PayLog 注解的方法");} }
public class PayServiceImpl {@PayLogpublic void pay() {System.out.println("执行支付逻辑");} }
当调用
pay()
方法时,就会触发切面逻辑。
3. 对比
特性 | execution | @annotation |
---|---|---|
匹配方式 | 方法签名(类、方法名、参数、返回值) | 方法上的注解 |
灵活性 | 精准匹配方法,但规则写起来可能冗长 | 给方法加注解即可,简洁直观 |
适用场景 | 横切关注点范围明确,比如整个包、某类的所有方法 | 只增强特定方法,比如需要日志、事务的方法 |
维护性 | 如果方法签名变动,切点表达式容易失效 | 注解写在方法上,不依赖方法名 |
✅ 总结
execution
→ 适合批量拦截,比如一个包下所有方法。@annotation
→ 适合精确控制,尤其是只想拦截某些“打标记”的方法。
三、AOP实现(执行顺序)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class PayAspect
{@Before("execution(public void com.donglin.interview2.aopreview.PayServiceImpl.pay(..))")public void beforeNotify(){System.out.println("-----@Before前置通知");}@After("execution(public void com.donglin.interview2.aopreview.PayServiceImpl.pay(..))")public void afterNotify(){System.out.println("-----@After后置通知");}@AfterReturning("execution(public void com.donglin.interview2.aopreview.PayServiceImpl.pay(..))")public void afterReturningNotify(){System.out.println("-----@AfterReturning返回通知");}@AfterThrowing("execution(public void com.donglin.interview2.aopreview.PayServiceImpl.pay(..))")public void afterThrowingNotify(){System.out.println("-----@AfterThrowing异常通知");}@Around("execution(public void com.donglin.interview2.aopreview.PayServiceImpl.pay(..))")public Object aroundNotify(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{Object retValue = null;System.out.println("-----@Around环绕通知AAA");retValue = proceedingJoinPoint.proceed();//放行System.out.println("-----@Around环绕通知BBB");return retValue;}
}
import cn.hutool.core.util.IdUtil;
import com.atguigu.interview2.aopreview.PayService;
import jakarta.annotation.Resource;
import org.springframework.boot.SpringBootVersion;
import org.springframework.core.SpringVersion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class PayAopReviewController
{@Resourceprivate PayService payService;@GetMapping(value = "/pay/aop")public String pay(){System.out.println("SpringVersion: "+ SpringVersion.getVersion()+"\t"+ "SpringBootVersion: "+ SpringBootVersion.getVersion());payService.pay();return IdUtil.simpleUUID();}
}
1. 代码含义
在 PayAspect
里,你定义了五种通知类型:
- @Before:方法执行前运行
- @After:方法执行后(无论是否异常)都会运行
- @AfterReturning:方法正常返回后运行
- @AfterThrowing:方法抛出异常时运行
- @Around:环绕通知,可以在方法前后都插入逻辑,还能决定是否调用目标方法
对应的切点是:
execution(public void com.donglin.interview2.aopreview.PayServiceImpl.pay(..))
也就是专门拦截 PayServiceImpl
里的 pay()
方法。
2. 实际执行顺序
在 Spring AOP 里,执行顺序大致是:
-
@Around
(进入时,前半段逻辑) -
@Before
-
目标方法执行
- 如果成功 → 进入
@AfterReturning
- 如果异常 → 进入
@AfterThrowing
- 如果成功 → 进入
-
@After
(无论成功还是失败都会执行) -
@Around
(退出时,后半段逻辑)
如果 pay()
方法里抛异常,顺序就会变成:
四、代理模式 vs AOP
回忆之前写的 PayProxy
手动代理,其实就是:
before()
→ 对应@Before
after()
→ 对应@After
- 中间执行目标方法 → 对应
payService.pay()
- 如果要加异常处理逻辑,也可以对应
@AfterThrowing
区别在于:
- 手写代理:一个类只能代理一个目标,扩展性差。
- AOP:Spring 自动生成代理,切点表达式可以选择很多目标方法,扩展性更强。
对比点 | 代理模式 | Spring AOP |
---|---|---|
实现方式 | 手写代理类,或用 JDK/CGLIB 动态代理 | 基于动态代理,Spring 自动生成代理 |
增强范围 | 单个目标类/接口 | 可批量拦截一类方法(execution/@annotation) |
扩展性 | 差,新增逻辑需改代理类 | 强,只需新增切面类 |
维护成本 | 高,类多时代理爆炸 | 低,非侵入式 |
适用场景 | 小型系统、教学案例 | 企业级系统,日志/事务/安全/缓存等 |