【从零入门23种设计模式12】结构型之代理模式(Spring AOP + 自定义注解 + 切面的实战)

作者:逆境不可逃

你的支持是我的动力,谢谢!!!

目录

一、代理模式核心定义

核心角色(所有代理模式通用)

生活类比(最易理解)

[二、代理模式的核心分类(实战中最常用的 3 类)](#二、代理模式的核心分类(实战中最常用的 3 类))

[三、实战案例 1:静态代理(入门必懂)](#三、实战案例 1:静态代理(入门必懂))

[1. 抽象主题:订单服务接口](#1. 抽象主题:订单服务接口)

[2. 真实主题:订单服务实现类(目标对象)](#2. 真实主题:订单服务实现类(目标对象))

[3. 代理类:订单服务静态代理](#3. 代理类:订单服务静态代理)

[4. 客户端调用](#4. 客户端调用)

输出结果

[四、实战案例 2:JDK 动态代理(Spring AOP 核心)](#四、实战案例 2:JDK 动态代理(Spring AOP 核心))

[1. JDK 动态代理核心类:InvocationHandler](#1. JDK 动态代理核心类:InvocationHandler)

[2. 客户端调用(可代理任意接口)](#2. 客户端调用(可代理任意接口))

输出结果

[五、实战案例 3:CGLIB 动态代理(无接口场景)](#五、实战案例 3:CGLIB 动态代理(无接口场景))

[1. 依赖准备](#1. 依赖准备)

[2. CGLIB 代理核心类:MethodInterceptor](#2. CGLIB 代理核心类:MethodInterceptor)

[3. 客户端调用(代理无接口类)](#3. 客户端调用(代理无接口类))

输出结果

六、代理模式的典型实战场景

[1. Spring AOP(核心场景)](#1. Spring AOP(核心场景))

[2. 远程代理(RPC 框架)](#2. 远程代理(RPC 框架))

[3. 延迟加载(虚拟代理)](#3. 延迟加载(虚拟代理))

[4. 保护代理(权限控制)](#4. 保护代理(权限控制))

[5. 缓存代理(结果复用)](#5. 缓存代理(结果复用))

七、代理模式与装饰器模式的区别(易混淆点)

总结

扩展

一、核心原理回顾

二、实战场景定义

[三、完整代码实现(Spring Boot 3.x)](#三、完整代码实现(Spring Boot 3.x))

[1. 依赖准备(Maven)](#1. 依赖准备(Maven))

[2. 步骤 1:定义自定义注解@ApiLog](#2. 步骤 1:定义自定义注解@ApiLog)

[3. 步骤 2:定义切面类(核心代理逻辑)](#3. 步骤 2:定义切面类(核心代理逻辑))

[4. 步骤 3:定义业务接口和实现类](#4. 步骤 3:定义业务接口和实现类)

[5. 步骤 4:启动类(开启 AOP)](#5. 步骤 4:启动类(开启 AOP))

[四、测试验证(Postman / 浏览器调用)](#四、测试验证(Postman / 浏览器调用))

[1. 测试查询用户接口(GET)](#1. 测试查询用户接口(GET))

[2. 测试创建用户接口(POST)](#2. 测试创建用户接口(POST))

[3. 测试异常接口(GET)](#3. 测试异常接口(GET))

五、核心知识点拆解(代理模式视角)

[1. Spring AOP 与动态代理的对应关系](#1. Spring AOP 与动态代理的对应关系)

[2. 关键注解说明](#2. 关键注解说明)

[3. JDK vs CGLIB 代理选择](#3. JDK vs CGLIB 代理选择)

六、生产级扩展建议

[1. 性能优化](#1. 性能优化)

[2. 功能扩展](#2. 功能扩展)

[3. 异常处理](#3. 异常处理)

总结


一、代理模式核心定义

代理模式是结构型设计模式的一种,核心目的是:

为其他对象提供一种代理以控制对这个对象的访问,代理对象在客户端和目标对象之间起到中介作用,可附加额外逻辑(如权限校验、日志、缓存、延迟加载)。

简单来说,就是 "中间人":你想访问目标对象,但不直接接触它,而是通过代理对象间接访问,代理可以帮你做 "前置处理(如鉴权)→ 调用目标 → 后置处理(如日志)"。

核心角色(所有代理模式通用)
角色 作用
抽象主题(Subject) 定义目标对象和代理对象的统一接口(如 "订单服务接口")
真实主题(RealSubject) 目标对象(被代理的核心对象),实现 Subject 接口(如 "订单服务实现类")
代理(Proxy) 实现 Subject 接口,持有 RealSubject 引用,控制对目标对象的访问,可附加额外逻辑
生活类比(最易理解)
  • 场景 1 :租房
    • 抽象主题:租房服务(找房源、签合同);
    • 真实主题:房东(有房源,能签合同);
    • 代理:中介(帮房东带看、收中介费、做背景核验,控制租客对房东的直接访问);
  • 场景 2 :明星演出
    • 抽象主题:演出服务(接演出、表演);
    • 真实主题:明星(核心表演);
    • 代理:经纪人(帮明星接演出、谈价格、安排行程,过滤无效请求)。

二、代理模式的核心分类(实战中最常用的 3 类)

代理模式的核心分类依据是代理对象的创建时机 ,不同分类适配不同场景:

分类 创建时机 实现方式 核心场景 优点 缺点
静态代理 编译期 手动编写代理类 简单场景、固定逻辑 简单直观、易调试 代码冗余、扩展差
动态代理(JDK) 运行期 反射 + 接口 接口化场景(如 Spring AOP) 无代码冗余、灵活 只能代理接口
动态代理(CGLIB) 运行期 字节码生成 + 继承 无接口场景 可代理任意类 不能代理 final 类 / 方法

三、实战案例 1:静态代理(入门必懂)

以 "订单服务代理" 为例,代理附加权限校验 + 日志记录逻辑:

1. 抽象主题:订单服务接口
复制代码
/**
 * 抽象主题:订单服务接口(目标+代理的统一接口)
 */
public interface OrderService {
    /**
     * 创建订单
     * @param userId 用户ID
     * @param productId 商品ID
     * @return 订单ID
     */
    String createOrder(String userId, String productId);
}
2. 真实主题:订单服务实现类(目标对象)
复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * 真实主题:订单服务核心实现(被代理的目标对象)
 */
@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Override
    public String createOrder(String userId, String productId) {
        // 核心业务逻辑:创建订单
        String orderId = "ORDER_" + System.currentTimeMillis() + "_" + userId;
        log.info("【核心业务】创建订单成功 | 订单ID:{} | 用户ID:{} | 商品ID:{}", orderId, userId, productId);
        return orderId;
    }
}
3. 代理类:订单服务静态代理
复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 静态代理:订单服务代理(附加权限校验+日志)
 */
public class OrderServiceStaticProxy implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceStaticProxy.class);
    // 持有目标对象引用
    private final OrderService target;

    public OrderServiceStaticProxy(OrderService target) {
        this.target = target;
    }

    @Override
    public String createOrder(String userId, String productId) {
        // 1. 前置处理:权限校验
        if (!checkPermission(userId)) {
            log.error("【权限校验】用户ID:{} 无创建订单权限", userId);
            throw new RuntimeException("无权限创建订单");
        }

        // 2. 前置处理:日志记录(入参)
        long startTime = System.currentTimeMillis();
        log.info("【代理日志】开始创建订单 | 用户ID:{} | 商品ID:{}", userId, productId);

        // 3. 调用目标对象核心方法
        String orderId;
        try {
            orderId = target.createOrder(userId, productId);
        } catch (Exception e) {
            // 异常处理
            log.error("【代理日志】创建订单异常 | 用户ID:{} | 商品ID:{}", userId, productId, e);
            throw e;
        }

        // 4. 后置处理:日志记录(耗时)
        long costTime = System.currentTimeMillis() - startTime;
        log.info("【代理日志】订单创建完成 | 订单ID:{} | 耗时:{}ms", orderId, costTime);

        return orderId;
    }

    /**
     * 权限校验逻辑(代理附加功能)
     */
    private boolean checkPermission(String userId) {
        // 模拟:仅白名单用户可创建订单
        return "U10086".equals(userId) || "U10010".equals(userId);
    }
}
4. 客户端调用
复制代码
public class StaticProxyClient {
    public static void main(String[] args) {
        // 1. 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 2. 创建代理对象(关联目标)
        OrderService proxy = new OrderServiceStaticProxy(target);

        // 3. 调用代理方法(间接访问目标)
        try {
            // 有权限用户
            String orderId1 = proxy.createOrder("U10086", "P8888");
            System.out.println("有权限用户创建订单结果:" + orderId1);

            // 无权限用户
            String orderId2 = proxy.createOrder("U9999", "P8888");
            System.out.println("无权限用户创建订单结果:" + orderId2);
        } catch (Exception e) {
            System.out.println("调用失败:" + e.getMessage());
        }
    }
}
输出结果
复制代码
【代理日志】开始创建订单 | 用户ID:U10086 | 商品ID:P8888
【核心业务】创建订单成功 | 订单ID:ORDER_1740400000000_U10086 | 用户ID:U10086 | 商品ID:P8888
【代理日志】订单创建完成 | 订单ID:ORDER_1740400000000_U10086 | 耗时:10ms
有权限用户创建订单结果:ORDER_1740400000000_U10086

【权限校验】用户ID:U9999 无创建订单权限
调用失败:无权限创建订单

四、实战案例 2:JDK 动态代理(Spring AOP 核心)

静态代理的问题:每加一个接口就要写一个代理类,代码冗余。动态代理可在运行期动态生成代理类,无需手动编写,是 Spring AOP 的底层实现。

1. JDK 动态代理核心类:InvocationHandler
复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理处理器(通用代理逻辑,可代理任意接口)
 */
public class JdkDynamicProxyHandler implements InvocationHandler {
    private static final Logger log = LoggerFactory.getLogger(JdkDynamicProxyHandler.class);
    // 目标对象(任意接口实现类)
    private final Object target;

    public JdkDynamicProxyHandler(Object target) {
        this.target = target;
    }

    /**
     * 代理核心方法:所有目标方法调用都会走这里
     * @param proxy 代理对象
     * @param method 目标方法
     * @param args 方法入参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 前置处理:通用权限校验(适配任意接口的userId参数)
        String userId = args != null && args.length > 0 ? args[0].toString() : "";
        if (!checkPermission(userId)) {
            log.error("【动态代理-权限校验】用户ID:{} 无权限调用方法:{}", userId, method.getName());
            throw new RuntimeException("无权限调用方法");
        }

        // 2. 前置处理:通用日志
        long startTime = System.currentTimeMillis();
        log.info("【动态代理-日志】开始调用方法:{} | 入参:{}", method.getName(), args);

        // 3. 调用目标方法
        Object result;
        try {
            result = method.invoke(target, args); // 反射调用目标方法
        } catch (Exception e) {
            log.error("【动态代理-异常】方法调用失败:{}", method.getName(), e);
            throw e;
        }

        // 4. 后置处理:通用耗时统计
        long costTime = System.currentTimeMillis() - startTime;
        log.info("【动态代理-日志】方法调用完成:{} | 结果:{} | 耗时:{}ms", method.getName(), result, costTime);

        return result;
    }

    /**
     * 生成代理对象(通用方法)
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 类加载器
                target.getClass().getInterfaces(),  // 目标对象实现的接口(JDK代理必须有接口)
                this                                // 代理处理器
        );
    }

    /**
     * 通用权限校验
     */
    private boolean checkPermission(String userId) {
        return "U10086".equals(userId) || "U10010".equals(userId);
    }
}
2. 客户端调用(可代理任意接口)
复制代码
public class JdkDynamicProxyClient {
    public static void main(String[] args) {
        // 1. 目标对象1:订单服务
        OrderService orderTarget = new OrderServiceImpl();
        // 2. 创建动态代理处理器
        JdkDynamicProxyHandler orderProxyHandler = new JdkDynamicProxyHandler(orderTarget);
        // 3. 生成代理对象
        OrderService orderProxy = orderProxyHandler.getProxy();

        // 4. 调用代理方法(订单服务)
        try {
            String orderId = orderProxy.createOrder("U10086", "P8888");
            System.out.println("订单服务调用结果:" + orderId);
        } catch (Exception e) {
            System.out.println("订单服务调用失败:" + e.getMessage());
        }

        // ------------------------------
        // 扩展:代理其他接口(如用户服务)
        // ------------------------------
        // 目标对象2:用户服务
        UserService userTarget = new UserServiceImpl();
        JdkDynamicProxyHandler userProxyHandler = new JdkDynamicProxyHandler(userTarget);
        UserService userProxy = userProxyHandler.getProxy();

        // 调用代理方法(用户服务)
        try {
            String userInfo = userProxy.getUserInfo("U10010");
            System.out.println("用户服务调用结果:" + userInfo);
        } catch (Exception e) {
            System.out.println("用户服务调用失败:" + e.getMessage());
        }
    }

    // 新增:用户服务接口(演示通用代理)
    interface UserService {
        String getUserInfo(String userId);
    }

    // 新增:用户服务实现
    static class UserServiceImpl implements UserService {
        @Override
        public String getUserInfo(String userId) {
            return "用户ID:" + userId + ",姓名:张三";
        }
    }
}
输出结果
复制代码
【动态代理-日志】开始调用方法:createOrder | 入参:[U10086, P8888]
【核心业务】创建订单成功 | 订单ID:ORDER_1740400000001_U10086 | 用户ID:U10086 | 商品ID:P8888
【动态代理-日志】方法调用完成:createOrder | 结果:ORDER_1740400000001_U10086 | 耗时:12ms
订单服务调用结果:ORDER_1740400000001_U10086

【动态代理-日志】开始调用方法:getUserInfo | 入参:[U10010]
【动态代理-日志】方法调用完成:getUserInfo | 结果:用户ID:U10010,姓名:张三 | 耗时:1ms
用户服务调用结果:用户ID:U10010,姓名:张三

五、实战案例 3:CGLIB 动态代理(无接口场景)

JDK 动态代理只能代理接口,CGLIB 可代理任意类(包括无接口类),通过生成目标类的子类实现代理(Spring 默认优先用 JDK,无接口则用 CGLIB)。

1. 依赖准备
复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
2. CGLIB 代理核心类:MethodInterceptor
复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

/**
 * CGLIB动态代理处理器(可代理任意类,无需接口)
 */
public class CglibDynamicProxyHandler implements MethodInterceptor {
    private static final Logger log = LoggerFactory.getLogger(CglibDynamicProxyHandler.class);
    // 目标对象(任意类)
    private final Object target;

    public CglibDynamicProxyHandler(Object target) {
        this.target = target;
    }

    /**
     * 生成代理对象(子类)
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
        enhancer.setCallback(this); // 设置回调(代理逻辑)
        return (T) enhancer.create(); // 生成子类(代理对象)
    }

    /**
     * 代理核心方法:目标方法调用都会走这里
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 1. 前置处理:通用日志
        long startTime = System.currentTimeMillis();
        log.info("【CGLIB代理-日志】开始调用方法:{} | 入参:{}", method.getName(), args);

        // 2. 调用目标方法(通过MethodProxy,比反射更快)
        Object result;
        try {
            result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法
        } catch (Exception e) {
            log.error("【CGLIB代理-异常】方法调用失败:{}", method.getName(), e);
            throw e;
        }

        // 3. 后置处理:耗时统计
        long costTime = System.currentTimeMillis() - startTime;
        log.info("【CGLIB代理-日志】方法调用完成:{} | 结果:{} | 耗时:{}ms", method.getName(), result, costTime);

        return result;
    }
}
3. 客户端调用(代理无接口类)
复制代码
public class CglibDynamicProxyClient {
    public static void main(String[] args) {
        // 目标对象:无接口的商品服务类
        ProductService productTarget = new ProductService();
        // 创建CGLIB代理处理器
        CglibDynamicProxyHandler proxyHandler = new CglibDynamicProxyHandler(productTarget);
        // 生成代理对象(子类)
        ProductService productProxy = proxyHandler.getProxy();

        // 调用代理方法
        String productInfo = productProxy.getProductInfo("P8888");
        System.out.println("CGLIB代理调用结果:" + productInfo);
    }

    // 无接口的商品服务类(演示CGLIB代理)
    static class ProductService {
        public String getProductInfo(String productId) {
            // 核心业务逻辑
            return "商品ID:" + productId + ",名称:小米14 Pro,价格:4999元";
        }
    }
}
输出结果
复制代码
【CGLIB代理-日志】开始调用方法:getProductInfo | 入参:[P8888]
【CGLIB代理-日志】方法调用完成:getProductInfo | 结果:商品ID:P8888,名称:小米14 Pro,价格:4999元 | 耗时:1ms
CGLIB代理调用结果:商品ID:P8888,名称:小米14 Pro,价格:4999元

六、代理模式的典型实战场景

1. Spring AOP(核心场景)
  • Spring AOP 的底层是JDK/CGLIB 动态代理
  • 代理附加逻辑:事务管理(@Transactional)、权限校验(@PreAuthorize)、日志(@Slf4j)、缓存(@Cacheable);
  • 示例:@Transactional注解本质是代理在方法执行前开启事务,执行后提交 / 回滚。
2. 远程代理(RPC 框架)
  • 如 Dubbo、Feign 的远程调用代理:
    • 本地代理对象:Feign 接口的动态代理;
    • 目标对象:远程服务器的服务;
    • 代理逻辑:封装网络请求、序列化 / 反序列化、负载均衡。
3. 延迟加载(虚拟代理)
  • 如 MyBatis 的 Mapper 接口代理:
    • 代理对象:Mapper 接口的动态代理;
    • 目标对象:SQL 执行逻辑(延迟加载);
    • 核心:调用 Mapper 方法时才执行 SQL,而非初始化时。
4. 保护代理(权限控制)
  • 如 Spring Security 的权限校验:
    • 代理对象:受保护的接口;
    • 代理逻辑:调用前校验用户权限,无权限则拒绝访问。
5. 缓存代理(结果复用)
  • 如 MyBatis 的一级 / 二级缓存:
    • 代理逻辑:调用方法前查缓存,有则直接返回,无则调用目标方法并缓存结果。

七、代理模式与装饰器模式的区别(易混淆点)

很多人会混淆代理模式和装饰器模式,核心区别如下:

特性 代理模式 装饰器模式
核心目的 控制访问(如鉴权、限流) 增强功能(如加日志、缓存)
对目标对象的感知 代理知道目标对象,客户端不知道 客户端知道目标对象,主动装饰
附加逻辑的位置 前置 / 后置(如先鉴权再调用) 环绕(增强核心逻辑)
组合方式 代理包裹目标对象 装饰器层层包裹
典型场景 AOP、RPC、权限控制 动态加功能、IO 流

总结

  1. 代理模式的核心是通过代理对象控制对目标对象的访问 ,可附加前置 / 后置逻辑(鉴权、日志、缓存等);
  2. 静态代理简单但冗余,JDK 动态代理(接口)和 CGLIB 动态代理(任意类)是实战主流,也是 Spring AOP 的底层;
  3. 代理模式是开发中最核心的设计模式之一,广泛应用于 AOP、RPC、权限控制、缓存等场景,掌握它能理解框架的核心设计思想。

扩展

基于代理模式实现一个Spring AOP + 自定义注解 + 切面的完整实战案例,我会贴合真实开发场景(接口日志 + 耗时统计 + 异常捕获),清晰拆解 Spring AOP 如何通过动态代理(JDK/CGLIB)实现无侵入的功能增强,代码可直接集成到 Spring Boot 项目中。

一、核心原理回顾

Spring AOP 的底层是动态代理模式

  • 抽象主题 :被代理的业务接口 / 类(如UserService);
  • 真实主题 :业务接口的实现类(如UserServiceImpl);
  • 代理对象 :Spring 动态生成的 JDK/CGLIB 代理类;
  • 代理逻辑 :切面(Aspect)中定义的额外功能(日志、耗时、异常),由 Spring 自动织入到目标方法的执行流程中。

二、实战场景定义

实现一个通用的接口增强注解@ApiLog,功能包括:

  1. 记录接口入参、出参;
  2. 统计接口执行耗时;
  3. 捕获接口异常并记录;
  4. 支持通过注解参数开关功能(如是否记录出参)。

三、完整代码实现(Spring Boot 3.x)

1. 依赖准备(Maven)
复制代码
<dependencies>
    <!-- Spring Boot核心 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>3.2.3</version>
    </dependency>
    <!-- Spring AOP核心 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>3.2.3</version>
    </dependency>
    <!-- JSON格式化 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.43</version>
    </dependency>
    <!-- Lombok(简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
2. 步骤 1:定义自定义注解@ApiLog
复制代码
import java.lang.annotation.*;

/**
 * 自定义注解:接口日志增强
 * 标注在方法/类上,触发AOP代理增强
 */
@Target({ElementType.METHOD, ElementType.TYPE}) // 可标注在方法/类上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,允许反射获取
@Documented // 生成文档
public @interface ApiLog {
    /**
     * 是否记录入参(默认true)
     */
    boolean recordInput() default true;

    /**
     * 是否记录出参(默认true)
     */
    boolean recordOutput() default true;

    /**
     * 是否统计耗时(默认true)
     */
    boolean recordCostTime() default true;

    /**
     * 接口描述(默认取方法名)
     */
    String desc() default "";
}
3. 步骤 2:定义切面类(核心代理逻辑)
复制代码
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;

/**
 * 切面类:实现@ApiLog注解的代理逻辑
 * 核心:通过Around通知环绕目标方法,添加日志/耗时/异常处理(动态代理的前置/后置逻辑)
 */
@Slf4j
@Aspect // 标记为切面类
@Component // 交给Spring管理
public class ApiLogAspect {

    /**
     * 定义切入点:匹配所有标注@ApiLog的方法/类
     */
    @Pointcut("@annotation(com.example.demo.annotation.ApiLog) || @within(com.example.demo.annotation.ApiLog)")
    public void apiLogPointcut() {}

    /**
     * 环绕通知:核心代理逻辑(对应动态代理的invoke方法)
     * ProceedingJoinPoint:连接点,包含目标方法、入参等信息
     */
    @Around("apiLogPointcut()")
    public Object aroundApiLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 获取注解配置和请求上下文
        ApiLog apiLog = getApiLogAnnotation(joinPoint);
        String requestId = "REQ_" + UUID.randomUUID().toString().substring(0, 16);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 2. 解析目标方法信息
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        String desc = apiLog.desc().isEmpty() ? methodName : apiLog.desc();

        try {
            // 3. 前置处理:记录入参(代理的前置逻辑)
            if (apiLog.recordInput()) {
                String params = JSON.toJSONString(joinPoint.getArgs());
                log.info("【API日志-开始】requestId={} | 接口描述={} | 类名={} | 方法名={} | 请求URL={} | 请求方式={} | 入参={}",
                        requestId, desc, className, methodName, request.getRequestURL(), request.getMethod(), params);
            }

            // 4. 调用目标方法(对应动态代理的method.invoke)
            long startTime = System.currentTimeMillis();
            Object result = joinPoint.proceed(); // 执行目标方法
            long costTime = System.currentTimeMillis() - startTime;

            // 5. 后置处理:记录出参+耗时(代理的后置逻辑)
            if (apiLog.recordOutput()) {
                String output = JSON.toJSONString(result);
                log.info("【API日志-成功】requestId={} | 接口描述={} | 耗时={}ms | 出参={}",
                        requestId, desc, costTime, output);
            } else if (apiLog.recordCostTime()) {
                log.info("【API日志-成功】requestId={} | 接口描述={} | 耗时={}ms",
                        requestId, desc, costTime);
            }

            return result;
        } catch (Exception e) {
            // 6. 异常处理:记录异常信息
            log.error("【API日志-异常】requestId={} | 接口描述={} | 类名={} | 方法名={} | 异常信息={}",
                    requestId, desc, className, methodName, e.getMessage(), e);
            throw e; // 抛出异常,不影响上层全局异常处理
        }
    }

    /**
     * 获取方法/类上的@ApiLog注解
     */
    private ApiLog getApiLogAnnotation(ProceedingJoinPoint joinPoint) {
        // 先获取方法上的注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ApiLog methodAnnotation = method.getAnnotation(ApiLog.class);
        if (methodAnnotation != null) {
            return methodAnnotation;
        }

        // 方法上没有则获取类上的注解
        Class<?> targetClass = joinPoint.getTarget().getClass();
        return targetClass.getAnnotation(ApiLog.class);
    }
}
4. 步骤 3:定义业务接口和实现类
复制代码
import com.example.demo.annotation.ApiLog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 业务控制器:演示@ApiLog注解的使用
 */
@RestController
// 类上标注:该类所有方法都启用日志(可被方法注解覆盖)
@ApiLog(desc = "用户管理接口", recordOutput = true)
public class UserController {

    /**
     * 示例1:查询用户(使用类上的注解配置)
     */
    @GetMapping("/user/query")
    public UserVO queryUser(String userId) {
        // 模拟业务逻辑:查询数据库
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return new UserVO(userId, "张三", "13800138000", 25);
    }

    /**
     * 示例2:创建用户(方法注解覆盖类注解)
     */
    @PostMapping("/user/create")
    @ApiLog(desc = "创建用户接口", recordInput = true, recordOutput = false) // 不记录出参,仅记录入参+耗时
    public String createUser(@RequestBody UserDTO userDTO) {
        // 模拟业务逻辑:插入数据库
        return "SUCCESS: 用户" + userDTO.getUserName() + "创建成功";
    }

    /**
     * 示例3:异常场景测试
     */
    @GetMapping("/user/exception")
    @ApiLog(desc = "异常测试接口")
    public String testException() {
        throw new RuntimeException("模拟接口异常:用户ID不存在");
    }

    // 数据模型
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class UserDTO {
        private String userName;
        private String phone;
        private Integer age;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class UserVO {
        private String userId;
        private String userName;
        private String phone;
        private Integer age;
    }
}
5. 步骤 4:启动类(开启 AOP)
复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * 启动类:开启AOP代理(Spring Boot 2.x+默认开启,显式声明更清晰)
 */
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) // proxyTargetClass=true:强制使用CGLIB代理(无接口也能代理)
public class AopProxyDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AopProxyDemoApplication.class, args);
    }
}

四、测试验证(Postman / 浏览器调用)

1. 测试查询用户接口(GET)

请求:http://localhost:8080/user/query?userId=U1001

日志输出:

复制代码
【API日志-开始】requestId=REQ_1234567890abcdef | 接口描述=用户管理接口 | 类名=com.example.demo.controller.UserController | 方法名=queryUser | 请求URL=http://localhost:8080/user/query | 请求方式=GET | 入参=["U1001"]
【API日志-成功】requestId=REQ_1234567890abcdef | 接口描述=用户管理接口 | 耗时=51ms | 出参={"age":25,"phone":"13800138000","userId":"U1001","userName":"张三"}
2. 测试创建用户接口(POST)

请求:

复制代码
POST http://localhost:8080/user/create
Body: {"userName":"李四","phone":"13800138001","age":30}

日志输出:

复制代码
【API日志-开始】requestId=REQ_abcdef1234567890 | 接口描述=创建用户接口 | 类名=com.example.demo.controller.UserController | 方法名=createUser | 请求URL=http://localhost:8080/user/create | 请求方式=POST | 入参=[{"age":30,"phone":"13800138001","userName":"李四"}]
【API日志-成功】requestId=REQ_abcdef1234567890 | 接口描述=创建用户接口 | 耗时=2ms
3. 测试异常接口(GET)

请求:http://localhost:8080/user/exception

日志输出:

复制代码
【API日志-开始】requestId=REQ_9876543210fedcba | 接口描述=异常测试接口 | 类名=com.example.demo.controller.UserController | 方法名=testException | 请求URL=http://localhost:8080/user/exception | 请求方式=GET | 入参=[]
【API日志-异常】requestId=REQ_9876543210fedcba | 接口描述=异常测试接口 | 类名=com.example.demo.controller.UserController | 方法名=testException | 异常信息=模拟接口异常:用户ID不存在

五、核心知识点拆解(代理模式视角)

1. Spring AOP 与动态代理的对应关系
动态代理组件 Spring AOP 组件 作用
InvocationHandler Aspect(切面) 封装代理逻辑(日志、耗时、异常)
invoke 方法 Around 通知 环绕目标方法,执行前置 / 核心 / 后置逻辑
method.invoke joinPoint.proceed() 调用目标方法(真实业务逻辑)
Proxy.newProxyInstance Spring 自动生成代理对象 根据目标类是否有接口,选择 JDK/CGLIB 生成代理对象
2. 关键注解说明
  • @Aspect:标记类为切面类(代理逻辑的容器);
  • @Pointcut:定义切入点(哪些方法需要被代理);
  • @Around:环绕通知(最灵活的通知类型,对应动态代理的 invoke 方法);
  • @EnableAspectJAutoProxy:开启 AOP 代理(Spring Boot 2.x + 可省略,默认开启)。
3. JDK vs CGLIB 代理选择
  • proxyTargetClass = false(默认):目标类有接口则用 JDK 代理,无则用 CGLIB;
  • proxyTargetClass = true:强制使用 CGLIB 代理(即使有接口);
  • 实战建议:设置proxyTargetClass = true,避免因接口问题导致代理失效(如 Controller 无接口)。

六、生产级扩展建议

1. 性能优化
  • 缓存 Method 对象:避免每次反射获取注解,提升性能;
  • 异步日志:日志写入异步执行,避免阻塞接口响应;
  • 过滤敏感参数:如密码、身份证号,日志中脱敏处理。
2. 功能扩展
  • 添加限流逻辑:在切面中集成 Guava RateLimiter,实现接口限流;
  • 添加权限校验:结合 Spring Security,校验用户权限;
  • 添加链路追踪:集成 SkyWalking/Zipkin,记录 traceId。
3. 异常处理
  • 自定义业务异常:捕获后返回友好提示;
  • 全局异常拦截:配合@ControllerAdvice,统一返回格式。

总结

  1. Spring AOP 的核心是动态代理模式 ,通过切面(Aspect)封装代理逻辑,无需修改业务代码即可实现功能增强;
  2. 自定义注解 + 切面是 Spring AOP 的经典用法,核心步骤:定义注解→定义切面(切入点 + 环绕通知)→业务方法标注注解;
  3. 环绕通知(@Around)对应动态代理的 invoke 方法,可灵活实现前置(入参日志)、核心(执行目标方法)、后置(出参 / 耗时)、异常处理逻辑。
相关推荐
电子科技圈2 小时前
IAR扩展嵌入式开发平台,推出面向安全关键型应用的长期支持(LTS)服务
嵌入式硬件·安全·设计模式·软件工程·代码规范·设计规范·代码复审
像少年啦飞驰点、2 小时前
Java策略模式从入门到实战:小白也能看懂的设计模式指南
java·设计模式·策略模式·编程入门·小白教程
程序员Terry2 小时前
别再用 if-else 堆砌代码了!策略模式让你的代码优雅十倍
java·设计模式
JTCC3 小时前
Java 设计模式西游篇 - 第八回:适配器模式通万国 女儿国语言无障碍
python·设计模式·适配器模式
逆境不可逃3 小时前
【从零入门23种设计模式17】行为型之中介者模式
java·leetcode·microsoft·设计模式·职场和发展·中介者模式
Anurmy3 小时前
设计模式之抽象工厂
设计模式
蜜獾云4 小时前
设计模式之原型模式:以自己为原型,自己实现自己的对象拷贝逻辑
java·设计模式·原型模式
melonbo4 小时前
C++ 中用于模块间通信的设计模式
开发语言·c++·设计模式
蜜獾云4 小时前
设计模式之简单工厂模式(4):创建对象时不会暴露创建逻辑
java·设计模式·简单工厂模式