代理模式 vs AOP:支付服务中的日志增强实践(含执行顺序详解)

目录

  • 一、实现支付服务
    • [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 注解的方法");
        }
    }
    java 复制代码
    public 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 里,执行顺序大致是:

  1. @Around(进入时,前半段逻辑)

  2. @Before

  3. 目标方法执行

    • 如果成功 → 进入 @AfterReturning
    • 如果异常 → 进入 @AfterThrowing
  4. @After(无论成功还是失败都会执行)

  5. @Around(退出时,后半段逻辑)

如果 pay() 方法里抛异常,顺序就会变成:

四、代理模式 vs AOP

回忆之前写的 PayProxy 手动代理,其实就是:

  • before() → 对应 @Before
  • after() → 对应 @After
  • 中间执行目标方法 → 对应 payService.pay()
  • 如果要加异常处理逻辑,也可以对应 @AfterThrowing

区别在于:

  • 手写代理:一个类只能代理一个目标,扩展性差。
  • AOP:Spring 自动生成代理,切点表达式可以选择很多目标方法,扩展性更强。
对比点 代理模式 Spring AOP
实现方式 手写代理类,或用 JDK/CGLIB 动态代理 基于动态代理,Spring 自动生成代理
增强范围 单个目标类/接口 可批量拦截一类方法(execution/@annotation)
扩展性 差,新增逻辑需改代理类 强,只需新增切面类
维护成本 高,类多时代理爆炸 低,非侵入式
适用场景 小型系统、教学案例 企业级系统,日志/事务/安全/缓存等
相关推荐
Zz_waiting.5 小时前
Spring Cloud 概述
后端·spring·spring cloud
西蓝花MQ5 小时前
Spring Cloud微服务篇面试题总结
spring·spring cloud·微服务
xyy202516 小时前
Spring事务的传播方式
java·数据库·spring
不能再留遗憾了21 小时前
【SpringCloud】Sentinel
spring·spring cloud·sentinel
whltaoin1 天前
AI 超级智能体全栈项目阶段五:RAG 四大流程详解、最佳实践与调优(基于 Spring AI 实现)
java·人工智能·spring·rag·springai
心勤则明1 天前
Spring AI 文档ETL实战:集成text-embedding-v4 与 Milvus
人工智能·spring·etl
艾菜籽1 天前
Spring Web MVC入门补充1
java·后端·spring·mvc
艾菜籽1 天前
Spring MVC入门补充2
java·spring·mvc
为java加瓦1 天前
Spring 方法注入机制深度解析:Lookup与Replace Method原理与应用
java·数据库·spring