目录
- 一、实现支付服务
-
- [1. 定义支付服务接口](#1. 定义支付服务接口)
- [2. 实现支付服务接口](#2. 实现支付服务接口)
- [3. 测试支付服务](#3. 测试支付服务)
- [4. 引入代理模式](#4. 引入代理模式)
- [5. 测试代理模式](#5. 测试代理模式)
- [6. 总结](#6. 总结)
- 二、execution和annotation区别
-
- [1. execution 表达式](#1. execution 表达式)
- [2. @annotation 表达式](#2. @annotation 表达式)
- [3. 对比](#3. 对比)
- 三、AOP实现(执行顺序)
- [四、代理模式 vs AOP](#四、代理模式 vs AOP)
一、实现支付服务
在本篇博客中,我们将展示如何使用Java中的接口和代理模式实现一个简单的支付服务系统。我们会先定义一个基础的支付服务接口,并为其提供一个实现类。接着,我们将通过代理模式为支付服务添加额外的行为,例如支付前后的日志输出。
1. 定义支付服务接口
首先,我们定义一个简单的PayService
接口,所有的支付方式都需要实现该接口。接口中包含一个pay()
方法,用于执行支付操作。
java
public interface PayService {
void pay(); // 支付操作
}
2. 实现支付服务接口
接下来,我们创建一个支付服务的实现类PayServiceImpl
,模拟微信支付的支付逻辑。我们将打印出一个模拟的支付交易号,并且暂时注释掉一些错误的代码(如除零错误),避免程序崩溃。
java
@Service
public class PayServiceImpl implements PayService {
@Override
public void pay() {
System.out.println("微信支付real " + IdUtil.simpleUUID());
// int age = 10 / 0; // 模拟错误,如果需要,可以开启这一行来测试异常
}
}
@Service
注解表明PayServiceImpl
是一个Spring的服务类,Spring会管理它的生命周期。IdUtil.simpleUUID()
用于生成一个唯一的UUID,模拟支付交易号。
3. 测试支付服务
在这里,我们简单测试一下PayServiceImpl
的实现:
java
public class ClientTest {
private static void payNormalV1() {
PayService payService = new PayServiceImpl();
payService.pay(); // 执行支付
}
public static void main(String[] args) {
payNormalV1(); // 调用普通支付
}
}
执行时,控制台会输出类似以下内容:
4. 引入代理模式
在实际开发中,我们经常使用代理模式来增强现有功能,比如增加日志记录、权限检查、事务管理等。在本例中,我们将使用代理模式包装PayService
,并在支付前后增加一些自定义逻辑(例如打印日志)。
java
public class PayProxy implements PayService {
private PayService payService; // 目标对象
// 构造注入
public PayProxy(PayService payService) {
this.payService = payService;
}
// 支付前的准备工作
private void before() {
System.out.println("-----before,打开微信支付");
}
@Override
public void pay() {
before(); // 执行支付前的操作
payService.pay(); // 委托目标对象执行实际的支付
after(); // 执行支付后的操作
}
// 支付后的操作
private void after() {
System.out.println("-----after,关闭微信支付");
}
}
在PayProxy
类中,我们通过构造器注入了一个PayService
实例。pay()
方法首先调用before()
方法打印日志,然后调用目标对象(即PayServiceImpl
)的pay()
方法,最后执行after()
方法,输出支付后的日志。
5. 测试代理模式
我们将创建一个新的测试方法来演示代理模式的使用:
java
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 表达式
-
作用:根据方法的签名(访问修饰符、返回值、包名、类名、方法名、参数)来定义切点。
-
特点:语法比较强大,可以非常精准地匹配方法。
-
示例:
java@Before("execution(* com.example.service.*.*(..))") public void beforeExecution() { System.out.println("通过 execution 拦截 service 包下的所有方法"); }
2. @annotation 表达式
-
作用:根据方法上是否存在某个注解来匹配切点。
-
特点 :更灵活,尤其适合只想增强带某个自定义注解的方法。
-
示例:
java// 定义一个自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PayLog { }
java@Aspect @Component public class PayAspect { // 匹配所有带 @PayLog 注解的方法 @Before("@annotation(com.example.annotation.PayLog)") public void beforeAnnotation() { System.out.println("通过 @annotation 拦截带 @PayLog 注解的方法"); } }
javapublic class PayServiceImpl { @PayLog public void pay() { System.out.println("执行支付逻辑"); } }
当调用
pay()
方法时,就会触发切面逻辑。
3. 对比
特性 | execution |
@annotation |
---|---|---|
匹配方式 | 方法签名(类、方法名、参数、返回值) | 方法上的注解 |
灵活性 | 精准匹配方法,但规则写起来可能冗长 | 给方法加注解即可,简洁直观 |
适用场景 | 横切关注点范围明确,比如整个包、某类的所有方法 | 只增强特定方法,比如需要日志、事务的方法 |
维护性 | 如果方法签名变动,切点表达式容易失效 | 注解写在方法上,不依赖方法名 |
✅ 总结
execution
→ 适合批量拦截,比如一个包下所有方法。@annotation
→ 适合精确控制,尤其是只想拦截某些"打标记"的方法。
三、AOP实现(执行顺序)
java
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;
}
}
java
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
{
@Resource
private 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:环绕通知,可以在方法前后都插入逻辑,还能决定是否调用目标方法
对应的切点是:
java
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) |
扩展性 | 差,新增逻辑需改代理类 | 强,只需新增切面类 |
维护成本 | 高,类多时代理爆炸 | 低,非侵入式 |
适用场景 | 小型系统、教学案例 | 企业级系统,日志/事务/安全/缓存等 |