大白话说Java设计模式-12-代理模式(业务实战篇)

📌 PDF:AI人工智能 — RAG知识库实战项目

大白话说Java设计模式-12-代理模式(业务实战篇):大白商城统一日志 / 权限 / 事务的"隐形管家"

📌 一句话本质:代理模式就是"找人代办",在不动原对象的前提下加新功能。

🏷️ 标签:代理模式 / JDK 动态代理 / CGLIB / Spring AOP / 大白商城   🎯 适合:初中级后端 / 想搞懂 AOP 原理的工程师


目录


一、业务场景引入:为什么需要"切面化"?

大白商城 2024 年初做了一次技术大改造,CTO 提了一个看似简单、实则"动全身"的需求:

“所有 Service 层的关键操作(如下单、支付、退款)都要加统一日志、性能监控、权限校验、事务管理。我不想每个 Service 都改一遍。”

我打开代码一看,100+ 个 Service 类,每个都有相似的样板代码

/**
 * 大白商城老的 Service 写法
 */
@Service
public class OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public Order createOrder(OrderRequest request) {
        // 1️⃣ 权限校验
        if (!permissionService.hasPermission(SecurityContext.getUserId(), "order:create")) {
            throw new BusinessException("无权限");
        }

        // 2️⃣ 性能监控开始
        long start = System.currentTimeMillis();

        // 3️⃣ 业务日志
        log.info("【OrderService】开始创建订单, userId={}", request.getUserId());

        try {
            // 4️⃣ 业务逻辑
            Order order = new Order(/* ... */);
            orderRepository.save(order);

            // 5️⃣ 业务日志
            log.info("【OrderService】创建订单成功, orderId={}", order.getId());

            return order;
        } catch (Exception e) {
            // 6️⃣ 错误日志
            log.error("【OrderService】创建订单失败", e);
            throw e;
        } finally {
            // 7️⃣ 性能监控结束
            long duration = System.currentTimeMillis() - start;
            metricsService.recordDuration("OrderService.createOrder", duration);
        }
    }
}

这种代码的痛点

序号问题后果
100+ Service 重复样板代码改一处要改 100 处
业务代码被横切逻辑污染看不出"业务在干啥"
横切逻辑散落各处改日志格式要改 100 个文件
新人接手难不知道哪行是业务、哪行是横切

老板要的"统一处理不加样板代码",怎么搞?

答案就是——代理模式 + AOP

1.1 大白话讲透代理

继续打比方:

场景:大白商城里所有 Service 都需要"日志 + 权限 + 性能监控"。每个 Service 自己写这些代码,等于每个员工自己去前台领门禁卡、去监控室登记。

  • 错误做法:每个 Service 内部写 log.info / permission.check / metrics.record(重复 100 遍)
  • 正确做法
    1. 雇一个"管家"(代理对象)站在 Service 门口
    2. 业务方调用 Service 时,实际调用的是管家
    3. 管家自动做日志 + 权限 + 性能监控
    4. 业务代码完全不变

管家怎么"代理"业务?

  • 管家和业务方实现同样的接口(业务方调用接口时不知道是管家还是原对象)
  • 管家在调用前后自动织入横切逻辑
  • 业务代码只关心业务,不管横切

代理模式 = 在不修改原对象的前提下,通过代理对象添加新功能

1.2 代理模式的 3 个真实场景

大白商城里,代理模式用在:

场景“代理”加的"功能"
Service 层 AOP业务 Service 的代理日志 + 权限 + 事务 + 监控
MyBatis MapperMapper 接口的代理动态 SQL + 结果集映射
Dubbo 调用远程服务接口的代理网络通信 + 序列化
Spring 事务@Transactional 注解的代理事务开启/提交/回滚
Redis 客户端RedisTemplate连接池 + 序列化

任何"横切关注点"(cross-cutting concern)都用代理


二、反面教材:散乱横切逻辑的"代码污染"

我们看 4 个反面教材,看它们是怎么一步步崩的。

2.1 反面教材 v1:每个方法手写横切逻辑

/**
 * ❌ 反面教材 v1:每个方法手写横切逻辑
 */
@Service
public class OrderServiceV1 {

    public Order createOrder(OrderRequest request) {
        // 1️⃣ 权限校验
        if (!permissionService.hasPermission("order:create")) {
            throw new BusinessException("无权限");
        }
        // 2️⃣ 性能监控
        long start = System.currentTimeMillis();
        log.info("【开始】创建订单, userId={}", request.getUserId());
        try {
            // 业务逻辑
            Order order = new Order(/* ... */);
            orderRepository.save(order);
            log.info("【成功】创建订单, orderId={}", order.getId());
            return order;
        } catch (Exception e) {
            log.error("【失败】创建订单", e);
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - start;
            metricsService.record("createOrder", duration);
        }
    }

    public Order cancelOrder(Long orderId) {
        // 同样的 7 行样板代码...
    }

    public Order refundOrder(Long orderId) {
        // 同样的 7 行样板代码...
    }
    // ... 每个方法都要写 7 行
}

翻车现场

序号问题后果
每个方法 7 行样板100 个方法 = 700 行重复
业务代码被污染看不到核心业务
改横切要改 100 处改日志格式要全改

2.2 反面教材 v2:父类继承横切逻辑

/**
 * ❌ 反面教材 v2:父类继承
 */
public abstract class BaseService {

    protected void before(String method) {
        if (!permissionService.hasPermission(method)) {
            throw new BusinessException("无权限");
        }
        long start = System.currentTimeMillis();
        log.info("【开始】{}, params={}", method, /* ... */);
    }

    protected void after(String method, Object result) {
        log.info("【成功】{}, result={}", method, result);
    }

    protected void exception(String method, Exception e) {
        log.error("【失败】{}", method, e);
    }
}

public class OrderServiceV2 extends BaseService {

    public Order createOrder(OrderRequest request) {
        before("createOrder");  // 调用父类
        try {
            Order order = new Order(/* ... */);
            orderRepository.save(order);
            after("createOrder", order);  // 调用父类
            return order;
        } catch (Exception e) {
            exception("createOrder", e);  // 调用父类
            throw e;
        }
    }
}

翻车现场

序号问题后果
继承关系僵硬Service 已经有父类怎么办?
多横切逻辑耦合日志 + 权限 + 事务全在父类,混在一起
不能选择性横切父类方法都执行,没法跳过

2.3 反面教材 v3:装饰器包装

/**
 * ❌ 反面教材 v3:装饰器
 */
public class OrderServiceWithLog implements OrderService {

    private final OrderService target;

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

    public Order createOrder(OrderRequest request) {
        log.info("【开始】创建订单");
        Order order = target.createOrder(request);
        log.info("【成功】创建订单");
        return order;
    }
}

public class OrderServiceWithAuth implements OrderService {

    private final OrderService target;

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

    public Order createOrder(OrderRequest request) {
        if (!permissionService.hasPermission("order:create")) {
            throw new BusinessException("无权限");
        }
        return target.createOrder(request);
    }
}

翻车现场

序号问题后果
类数量爆炸100 个 Service × 3 个横切 = 300 个类
顺序问题先日志后权限?还是先权限后日志?
仍然有样板每个装饰器要写"转发 + 横切"代码

2.4 反面教材 v4:完全不用横切(裸奔)

/**
 * ❌ 反面教材 v4:完全不用横切
 */
public class OrderServiceV4 {

    public Order createOrder(OrderRequest request) {
        // 业务逻辑,没日志、没权限、没监控
        Order order = new Order(/* ... */);
        orderRepository.save(order);
        return order;
    }
}

翻车现场

序号问题后果
出问题无法排查没日志,线上事故追查无门
权限漏洞没校验,敏感操作可被任意调用
性能瓶颈难发现没监控,不知道哪些方法慢

2.5 4 个反面教材的共同病根

痛点反模式方案能不能解决?
业务代码不被污染❌ 全部要写样板
加新横切不动业务❌ 全部要改老代码
多个横切灵活组合❌ 全部要改继承结构
性能可控❌ 反射有性能损耗

必须上代理模式 + AOP


三、模式原理:代理的"两件套 + 一张图"

3.1 代理的 3 个核心角色

角色职责例子
抽象主题(Subject)定义代理和真实对象的共同接口OrderService 接口
真实主题(Real Subject)真正干活的对象OrderServiceImpl
代理主题(Proxy)包装真实对象,加新功能OrderServiceProxy

关键点代理和真实对象实现同一接口,调用方无感知。

3.2 一张图看懂代理

调用方
  ↓
OrderService (接口)
  ↓
┌──────────────┬──────────────┐
│              │              │
▼              ▼              ▼
┌─────────┐  ┌─────────────┐  ┌─────────────┐
│真实对象 │  │ JDK 动态代理  │  │ CGLIB 代理  │
│Impl     │  │  (基于接口)   │  │ (基于继承)  │
│         │  │              │  │             │
│ 业务逻辑 │  │ 业务逻辑      │  │ 业务逻辑    │
│         │  │ + 日志        │  │ + 日志      │
│         │  │ + 权限        │  │ + 权限      │
│         │  │ + 事务        │  │ + 事务      │
└─────────┘  └─────────────┘  └─────────────┘

3.3 代理的"灵魂三问"

Q1:代理 vs 装饰器,区别是什么?

答:

  • 代理控制访问(权限 / 远程 / 延迟加载)
  • 装饰器添加职责(增强功能 / 包装)
  • 代理侧重"管控"装饰器侧重"增强"
  • Spring AOP 本质是代理(不是装饰器)

Q2:JDK 动态代理 vs CGLIB,区别是什么?

答:

  • JDK 动态代理基于接口(必须实现接口),用 Proxy.newProxyInstance() 创建
  • CGLIB 代理基于继承(不需要接口),用字节码生成子类
  • JDK 代理性能更好但有限制,CGLIB 代理更灵活但启动慢

Q3:Spring AOP 怎么"无侵入"工作?

答:

  • 编译期 / 运行期生成代理对象
  • 业务代码完全不知道代理存在
  • 调用方注入的是代理对象(不是原对象)
  • 代理对象在调用前后织入横切逻辑

3.4 代理的 4 种写法

写法适用场景缺点
静态代理简单场景类数量爆炸
JDK 动态代理有接口必须有接口
CGLIB 代理无接口不能代理 final 类
Spring AOP企业级强依赖 Spring

大白商城主推 Spring AOP(底层是 JDK 动态代理 + CGLIB 混合)。


四、实战代码:大白商城代理完整实现

下面是大白商城生产环境在用的代理实现,全套代码可直接复制到 IDEA 跑

4.1 项目环境与依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.dabai.mall</groupId>
    <artifactId>mall-design-pattern-12</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>mall-design-pattern-12</name>
    <description>大白商城 - 设计模式 12 代理模式</description>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.2 抽象主题:OrderService 接口

package com.dabai.mall.order;

/**
 * ✅ 抽象主题:订单服务接口
 * <p>
 * 代理和真实对象都实现这个接口
 *
 * @author 大白商城技术团队
 */
public interface OrderService {

    /**
     * 创建订单
     */
    Order createOrder(OrderRequest request);

    /**
     * 取消订单
     */
    Order cancelOrder(Long orderId);

    /**
     * 退款
     */
    Order refundOrder(Long orderId);
}

4.3 真实主题:OrderServiceImpl

package com.dabai.mall.order;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * ✅ 真实主题:订单服务实现
 * <p>
 * 业务代码完全干净,没有任何横切逻辑
 *
 * @author 大白商城技术团队
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order createOrder(OrderRequest request) {
        log.info("【业务】开始创建订单, userId={}", request.getUserId());

        // 业务逻辑(干净)
        Order order = new Order();
        order.setId(System.currentTimeMillis());
        order.setOrderNo("ORDER_" + System.currentTimeMillis());
        order.setUserId(request.getUserId());
        order.setPayAmount(request.getAmount());

        log.info("【业务】创建订单成功, orderId={}", order.getId());
        return order;
    }

    @Override
    public Order cancelOrder(Long orderId) {
        log.info("【业务】取消订单, orderId={}", orderId);
        Order order = new Order();
        order.setId(orderId);
        order.setOrderStatus("CANCELLED");
        return order;
    }

    @Override
    public Order refundOrder(Long orderId) {
        log.info("【业务】退款, orderId={}", orderId);
        Order order = new Order();
        order.setId(orderId);
        order.setOrderStatus("REFUNDED");
        return order;
    }
}

配套 DTO

package com.dabai.mall.order;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderRequest {

    private Long userId;
    private BigDecimal amount;
    private String productInfo;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Order {
    private Long id;
    private String orderNo;
    private Long userId;
    private BigDecimal payAmount;
    private String orderStatus;
}

4.4 实现一:JDK 动态代理

package com.dabai.mall.proxy.jdk;

import com.dabai.mall.order.OrderService;
import lombok.extern.slf4j.Slf4j;

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

/**
 * ✅ JDK 动态代理
 * <p>
 * 核心:基于接口 + InvocationHandler
 */
@Slf4j
public class JdkProxyFactory {

    /**
     * 创建 JDK 动态代理
     */
    public static <T> T createProxy(T target, InvocationHandler handler) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),      // 类加载器
                target.getClass().getInterfaces(),         // 目标对象实现的接口
                handler                                   // 调用处理器
        );
    }

    /**
     * 默认的横切逻辑处理器(日志 + 性能监控)
     */
    public static InvocationHandler defaultHandler(Object target) {
        return (proxy, method, args) -> {
            long start = System.currentTimeMillis();

            // ✅ 横切:日志
            log.info("【JDK代理-开始】{}, params={}", method.getName(), args);

            try {
                // ✅ 调用原方法
                Object result = method.invoke(target, args);

                // ✅ 横切:日志
                log.info("【JDK代理-成功】{}, result={}", method.getName(), result);
                return result;
            } catch (Exception e) {
                // ✅ 横切:错误日志
                log.error("【JDK代理-失败】{}", method.getName(), e);
                throw e;
            } finally {
                long duration = System.currentTimeMillis() - start;
                log.info("【JDK代理-耗时】{}, duration={}ms", method.getName(), duration);
            }
        };
    }
}

使用示例

/**
 * ✅ 用 JDK 动态代理包装
 */
public class JdkProxyDemo {

    public static void main(String[] args) {
        // 真实对象
        OrderServiceImpl realService = new OrderServiceImpl();

        // ✅ 创建代理
        OrderService proxy = JdkProxyFactory.createProxy(
                realService,
                JdkProxyFactory.defaultHandler(realService)
        );

        // 调用代理(实际调用原方法,但带了横切)
        OrderRequest request = OrderRequest.builder()
                .userId(1001L)
                .amount(new BigDecimal("8999.00"))
                .build();

        Order order = proxy.createOrder(request);
    }
}

关键点解读

代码解读
Proxy.newProxyInstance(...)JDK 反射创建运行时生成代理类
target.getClass().getInterfaces()目标接口代理必须实现这些接口
method.invoke(target, args)反射调用真正调用原方法
InvocationHandler横切逻辑织入日志/性能监控

4.5 实现二:CGLIB 代理

package com.dabai.mall.proxy.cglib;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * ✅ CGLIB 代理
 * <p>
 * 核心:基于继承(不需要接口)
 */
@Slf4j
public class CglibProxyFactory {

    /**
     * 创建 CGLIB 代理
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Class<T> targetClass, MethodInterceptor interceptor) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);   // 设置父类(继承)
        enhancer.setCallback(interceptor);      // 设置拦截器
        return (T) enhancer.create();           // 创建代理对象
    }

    /**
     * 默认的横切逻辑拦截器
     */
    public static MethodInterceptor defaultInterceptor(Class<?> targetClass) {
        return (obj, method, args, proxy) -> {
            long start = System.currentTimeMillis();

            // ✅ 横切:日志
            log.info("【CGLIB代理-开始】{}, params={}", method.getName(), args);

            try {
                // ✅ 调用原方法(用 proxy.invokeSuper 避免递归)
                Object result = proxy.invokeSuper(obj, args);

                log.info("【CGLIB代理-成功】{}, result={}", method.getName(), result);
                return result;
            } catch (Throwable e) {
                log.error("【CGLIB代理-失败】{}", method.getName(), e);
                throw e;
            } finally {
                long duration = System.currentTimeMillis() - start;
                log.info("【CGLIB代理-耗时】{}, duration={}ms", method.getName(), duration);
            }
        };
    }
}

使用示例

/**
 * ✅ 用 CGLIB 代理包装
 */
public class CglibProxyDemo {

    public static void main(String[] args) {
        // ✅ CGLIB 不需要接口,直接代理类
        OrderServiceImpl proxy = CglibProxyFactory.createProxy(
                OrderServiceImpl.class,
                CglibProxyFactory.defaultInterceptor(OrderServiceImpl.class)
        );

        OrderRequest request = OrderRequest.builder()
                .userId(1001L)
                .amount(new BigDecimal("8999.00"))
                .build();

        Order order = proxy.createOrder(request);
    }
}

关键点解读

代码解读
EnhancerCGLIB 核心字节码生成
setSuperclass(targetClass)设置父类代理继承原类
proxy.invokeSuper(obj, args)调用父类不要用 method.invoke(会递归)
不需要接口灵活可代理普通类

4.6 实现三:Spring AOP

package com.dabai.mall.proxy.springaop;

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.springframework.stereotype.Component;

/**
 * ✅ Spring AOP 切面
 * <p>
 * 核心:@Aspect + @Around 注解
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 切点:所有 Service 类的 public 方法
     */
    @Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
    public void servicePointcut() {}

    /**
     * 环绕通知:日志 + 性能监控
     */
    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        // ✅ 横切:日志
        log.info("【AOP-开始】{}, params={}", methodName, args);

        try {
            // ✅ 调用原方法
            Object result = joinPoint.proceed();

            log.info("【AOP-成功】{}, result={}", methodName, result);
            return result;
        } catch (Throwable e) {
            log.error("【AOP-失败】{}", methodName, e);
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - start;
            log.info("【AOP-耗时】{}, duration={}ms", methodName, duration);
        }
    }
}

使用示例

/**
 * ✅ Spring AOP 自动织入
 * <p>
 * 注意:OrderServiceImpl 完全不用改,AOP 自动织入
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order createOrder(OrderRequest request) {
        // 纯业务代码,没有横切
        // AOP 自动在前后织入日志 + 性能监控
        return new Order();
    }
}

关键点解读

注解作用
@Aspect标记为切面
@Pointcut定义切点(哪些方法要拦截)
@Around环绕通知(前后都织入)
ProceedingJoinPoint切点对象(可获取方法名/参数/调用 proceed)

4.7 单元测试

package com.dabai.mall.proxy;

import com.dabai.mall.order.Order;
import com.dabai.mall.order.OrderRequest;
import com.dabai.mall.order.OrderService;
import com.dabai.mall.order.OrderServiceImpl;
import com.dabai.mall.proxy.cglib.CglibProxyFactory;
import com.dabai.mall.proxy.jdk.JdkProxyFactory;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Proxy;
import java.math.BigDecimal;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 代理完整单元测试
 */
class ProxyTest {

    /**
     * 测试 1:JDK 动态代理
     */
    @Test
    void testJdkDynamicProxy() {
        OrderServiceImpl realService = new OrderServiceImpl();

        // ✅ 创建代理
        OrderService proxy = (OrderService) Proxy.newProxyInstance(
                OrderServiceImpl.class.getClassLoader(),
                new Class<?>[]{OrderService.class},
                JdkProxyFactory.defaultHandler(realService)
        );

        // 验证:代理是 OrderService 类型
        assertNotNull(proxy);
        assertTrue(proxy instanceof OrderService);
        assertFalse(proxy instanceof OrderServiceImpl, "代理不是真实对象");

        // 调用
        OrderRequest request = OrderRequest.builder()
                .userId(1001L)
                .amount(new BigDecimal("8999.00"))
                .build();

        Order order = proxy.createOrder(request);
        assertNotNull(order);
        assertNotNull(order.getId());
    }

    /**
     * 测试 2:CGLIB 代理
     */
    @Test
    void testCglibProxy() {
        // ✅ CGLIB 不需要接口
        OrderServiceImpl proxy = CglibProxyFactory.createProxy(
                OrderServiceImpl.class,
                CglibProxyFactory.defaultInterceptor(OrderServiceImpl.class)
        );

        assertNotNull(proxy);

        // 调用
        OrderRequest request = OrderRequest.builder()
                .userId(1002L)
                .amount(new BigDecimal("100.00"))
                .build();

        Order order = proxy.createOrder(request);
        assertNotNull(order);
    }

    /**
     * 测试 3:JDK 代理的 getClass() 是动态生成的
     */
    @Test
    void testProxyClassName() {
        OrderServiceImpl realService = new OrderServiceImpl();

        OrderService proxy = (OrderService) Proxy.newProxyInstance(
                OrderServiceImpl.class.getClassLoader(),
                new Class<?>[]{OrderService.class},
                JdkProxyFactory.defaultHandler(realService)
        );

        // 代理的 class 名是 $Proxy0、$Proxy1...
        assertTrue(proxy.getClass().getName().contains("$Proxy"));
    }
}

五、JDK 动态代理 vs CGLIB 代理:怎么选?

5.1 一张表看清核心区别

维度JDK 动态代理CGLIB 代理
原理基于接口(反射)基于继承(字节码)
前提条件必须有接口不需要接口
性能较优(JDK 8+)启动慢,调用快
final 类✅ 都能代理❌ 不能代理 final 类
final 方法✅ 都能代理❌ 不能代理 final 方法
生成方式Proxy.newProxyInstanceEnhancer.create
Spring 默认有接口时用无接口时用

5.2 Spring 的默认策略

/**
 * Spring AOP 代理选择逻辑(简化版)
 */
if (targetClass.getInterfaces().length > 0) {
    // 有接口:用 JDK 动态代理
    return new JdkDynamicAopProxy(target);
} else {
    // 无接口:用 CGLIB
    return new CglibAopProxy(target);
}

Spring Boot 2.0+ 默认行为

情况用的代理
类有接口JDK 动态代理
类无接口CGLIB 代理
proxyTargetClass=true强制 CGLIB(不管有没有接口)

5.3 性能对比

操作JDK 动态代理CGLIB 代理
启动时间快(10ms)慢(100ms,需生成字节码)
单次调用100ns50ns(CGLIB 调用更快)
内存占用较小较大(生成新类)

业务启示

频繁调用的方法用 CGLIB,启动期一次性创建调用少的方法用 JDK,简单高效

5.4 大白商城的选型

场景用哪个原因
Service 层(实现接口)JDK 动态代理Spring 默认
第三方类(无接口)CGLIB 代理必须用 CGLIB
MyBatis MapperJDK 动态代理Mapper 是接口
工具类(static 方法)❌ 不能代理static 不能被代理
final 类❌ 不能代理CGLIB 限制

六、Spring AOP 怎么"无侵入"工作?

6.1 Spring AOP 的"魔法"

/**
 * ✅ 业务代码完全干净
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order createOrder(OrderRequest request) {
        // 没有任何横切代码
        return new Order();
    }
}

但是调用 orderService.createOrder(),自动有:

  • 日志
  • 性能监控
  • 权限校验
  • 事务管理

怎么做到的?

调用方注入 OrderService
        ↓
Spring 容器实际注入的是代理对象
        ↓
代理对象内部持有 OrderServiceImpl(真实对象)
        ↓
代理对象在调用真实对象前后织入横切逻辑
        ↓
返回结果给调用方

6.2 Spring AOP 的"织入时机"

织入时机方式性能
编译期织入AspectJ 编译器最好(运行时零开销)
类加载期织入AspectJ 加载期织入
运行期织入Spring AOP(默认)一般(每次调用有开销)

Spring AOP 默认是"运行期织入"——通过动态代理在运行时生成代理对象。

6.3 Spring AOP 的 5 种通知

通知时机用途
@Before方法执行前权限校验
@After方法执行后(无论成功失败)资源清理
@AfterReturning方法成功返回后正常日志
@AfterThrowing方法抛异常后错误日志
@Around包裹方法(最强大)性能监控 / 事务

完整切面示例

/**
 * ✅ 完整的 Spring AOP 切面
 */
@Slf4j
@Aspect
@Component
public class CompleteAspect {

    @Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
    public void servicePointcut() {}

    @Before("servicePointcut()")
    public void before() {
        log.info("【@Before】方法执行前");
    }

    @After("servicePointcut()")
    public void after() {
        log.info("【@After】方法执行后");
    }

    @AfterReturning("servicePointcut()")
    public void afterReturning() {
        log.info("【@AfterReturning】方法成功返回");
    }

    @AfterThrowing("servicePointcut()")
    public void afterThrowing() {
        log.info("【@AfterThrowing】方法抛异常");
    }

    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            log.info("【@Around】耗时: {}ms", System.currentTimeMillis() - start);
        }
    }
}

七、日志 + 权限 + 事务三合一切面

7.1 完整的多切面组合

/**
 * ✅ 大白商城"日志 + 权限 + 事务"三合一切面
 */
@Slf4j
@Aspect
@Component
@Order(1)  // 切面顺序:数字越小越先执行
public class LogAspect {

    @Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
    public void servicePointcut() {}

    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        String methodName = pjp.getSignature().getName();
        log.info("【日志】开始, method={}, params={}", methodName, pjp.getArgs());

        try {
            Object result = pjp.proceed();
            log.info("【日志】成功, method={}, result={}", methodName, result);
            return result;
        } catch (Throwable e) {
            log.error("【日志】失败, method={}", methodName, e);
            throw e;
        } finally {
            log.info("【日志】耗时, method={}, duration={}ms",
                    methodName, System.currentTimeMillis() - start);
        }
    }
}

@Slf4j
@Aspect
@Component
@Order(2)
public class PermissionAspect {

    @Autowired
    private PermissionService permissionService;

    @Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
    public void servicePointcut() {}

    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
        // 这里简化:用方法名作为权限标识
        String methodName = joinPoint.getSignature().getName();
        String permission = "service:" + methodName;

        Long userId = SecurityContext.getUserId();
        if (!permissionService.hasPermission(userId, permission)) {
            throw new BusinessException("无权限: " + permission);
        }

        log.info("【权限】通过, userId={}, permission={}", userId, permission);
    }
}

@Slf4j
@Aspect
@Component
@Order(0)  // 事务最先开始
public class TransactionAspect {

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
    public void servicePointcut() {}

    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        TransactionStatus status = transactionManager.getTransaction(
                new DefaultTransactionDefinition());

        try {
            Object result = pjp.proceed();
            transactionManager.commit(status);
            log.info("【事务】提交成功, method={}", pjp.getSignature().getName());
            return result;
        } catch (Throwable e) {
            transactionManager.rollback(status);
            log.error("【事务】回滚, method={}", pjp.getSignature().getName(), e);
            throw e;
        }
    }
}

7.2 切面执行顺序

调用 orderService.createOrder()
        ↓
【@Order(0)】TransactionAspect.around 开始
        ↓ 开启事务
【@Order(1)】LogAspect.around 开始
        ↓ 记录开始日志
【@Order(2)】PermissionAspect.before
        ↓ 权限校验
        ↓
   OrderServiceImpl.createOrder() 业务执行
        ↓
【@Order(2)】PermissionAspect.after(无操作)
        ↓
【@Order(1)】LogAspect.around 结束
        ↓ 记录成功日志 + 耗时
【@Order(0)】TransactionAspect.around 结束
        ↓ 提交事务
        ↓
返回结果

7.3 业务代码"完全干净"

/**
 * ✅ 业务代码完全干净,没有任何横切逻辑
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order createOrder(OrderRequest request) {
        // 纯业务逻辑
        // 自动有:日志 + 权限 + 事务
        Order order = new Order();
        order.setId(System.currentTimeMillis());
        return order;
    }
}

八、工程决策 Checklist

8.1 ✅ 这 5 种情况,强烈建议用代理

序号场景原因
横切关注点(日志/权限/事务)业务代码保持干净
远程调用(RPC/HTTP)隐藏网络细节
延迟加载(Hibernate)性能优化
访问控制(敏感操作)统一权限管理
缓存(读多写少)自动缓存

8.2 ❌ 这 5 种情况,绝对不要用代理

序号场景原因
业务逻辑简单代理是多余的
性能敏感反射有性能损耗
static 方法static 不能被代理
final 类 / final 方法CGLIB 限制
构造方法中需要横切代理不拦截构造方法

8.3 ⚠️ 代理的 6 大常见坑

序号表现解决方案
自调用失效this.method() 不走代理注入自己的代理
final 类无法代理CGLIB 抛异常用接口或 JDK 代理
static 方法无法代理代理被绕过改用普通方法
private 方法无法代理代理不拦截改用 public 方法
异常被吃掉InvocationHandler 包装异常注意 getCause()
性能损耗反射调用慢缓存 Method 对象

8.4 面试官视角:代理高频追问

Q1:JDK 动态代理 vs CGLIB,区别是什么?

答:

  • JDK 动态代理:基于接口,必须有接口,用 Proxy.newProxyInstance
  • CGLIB:基于继承,不需要接口,但不能代理 final 类/方法
  • Spring 默认:有接口用 JDK,无接口用 CGLIB

Q2:Spring AOP 是怎么"无侵入"工作的?

答:Spring 在运行时通过动态代理生成代理对象,业务代码完全不知道。调用方注入的是代理对象,代理对象在调用真实对象前后织入横切逻辑

Q3:代理模式 vs 装饰器,区别是什么?

答:代理模式控制访问(权限/远程/延迟),装饰器模式增强功能(加职责)。Spring AOP 本质是代理,而不是装饰器。


九、与其他模式协作

9.1 代理 + 工厂方法 = 工厂创建代理

场景:用工厂方法批量创建代理对象。

/**
 * 工厂方法 + 代理
 */
public class ServiceProxyFactory {

    public static <T> T createProxy(T target, InvocationHandler handler) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );
    }
}

9.2 代理 + 单例 = 代理本身单例

场景:代理对象可以做成单例,避免重复创建。

/**
 * 代理 + 单例:全局共享一个代理
 */
public class GlobalLogProxy {

    private static final InvocationHandler HANDLER = (proxy, method, args) -> {
        // 统一的日志处理
        return method.invoke(/* ... */);
    };

    public static <T> T wrap(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                HANDLER
        );
    }
}

9.3 代理 + 责任链 = 多个代理串联

场景:多个横切逻辑串联执行(Spring AOP 内部就是这样做的)。

/**
 * 代理 + 责任链:多个拦截器串联
 */
public class ChainProxy {

    public static <T> T create(T target, List<MethodInterceptor> interceptors) {
        T current = target;
        for (MethodInterceptor interceptor : interceptors) {
            current = (T) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    (proxy, method, args) -> interceptor.intercept(current, method, args)
            );
        }
        return current;
    }
}

9.4 大白商城模式协作全景图

                    ┌──────────────┐
                    │    代理     │ ← 本篇
                    └──────┬───────┘
                           │
       ┌───────────┬───────┼───────┬───────────┐
       │           │       │       │           │
   ┌───▼───┐  ┌────▼───┐ ┌▼────┐ ┌▼─────┐  ┌───▼────┐
   │工厂方法│  │ 单例   │ │责任链│ │策略  │  │ 装饰器 │
   │(创建) │  │(共享) │ │(串联)│ │(算法)│  │(增强) │
   └───────┘  └────────┘ └──────┘ └──────┘  └───────┘
   04 篇       02 篇      31 篇    25 篇     13 篇

十、本篇小结 + 下篇预告

10.1 本篇小结(5 个核心要点)

  1. 本质:代理 = 在不动原对象的前提下加新功能,横切关注点最佳方案
  2. 场景:日志/权限/事务/远程调用,任何横切都用代理
  3. JDK vs CGLIB:JDK 基于接口、CGLIB 基于继承,Spring 默认混合
  4. Spring AOP:运行时织入,业务代码完全干净
  5. 避坑:自调用失效 / final 不能代理 / static 不能代理。

10.2 一句话总结

代理不是"换种方式调方法",是"在调用前后自动织入横切逻辑"。大白商城 100+ Service 类的统一日志/权限/事务,全靠 Spring AOP 自动代理——业务代码一行横切逻辑都不写。

10.3 知识脑图

代理模式
├── 4 大角色
│   ├── 抽象主题(OrderService 接口)
│   ├── 真实主题(OrderServiceImpl)
│   ├── 代理主题(JdkProxy / CglibProxy / SpringAOP)
│   └── 客户端(Controller)
├── 实战要点
│   ├── 完整 pom + 3 个测试
│   ├── 3 种实现对比(JDK/CGLIB/Spring AOP)
│   └── 业务代码完全干净
├── JDK vs CGLIB
│   ├── JDK:基于接口,性能优
│   ├── CGLIB:基于继承,更灵活
│   └── Spring 默认混合
├── Spring AOP
│   ├── 5 种通知
│   ├── 3 个切面组合(日志/权限/事务)
│   └── @Order 控制切面顺序
└── 模式协作
    ├── + 工厂方法(批量创建代理)
    ├── + 单例(共享代理)
    └── + 责任链(多个代理串联)

10.4 下篇预告

第 13 篇【代理模式 - 源码剖析篇】:JDK / CGLIB / Spring AOP 中的代理实现

下一篇我们会深入源码,回答三个问题:

  1. JDK 的 Proxy.newProxyInstance() 怎么生成代理类?
  2. CGLIB 的 Enhancer.create() 怎么生成子类?
  3. Spring 的 JdkDynamicAopProxy / CglibAopProxy 怎么织入横切逻辑?

并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机小能手(AI+Java)

若对您有所帮助,请点点关注哟~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值