Java 编程之代理模式

前言

代理模式(Proxy Pattern)是 Java 设计模式中的经典结构型模式,常用于控制对象访问,增强功能,延迟加载等场景。本篇将由浅入深,详细解析静态代理、动态代理、JDK 与 CGLIB 实现,以及实战应用和常见误区。

一、什么是代理模式?

代理模式:为其他对象提供一种代理以控制对这个对象的访问。

好比你想访问一个资源,但并不直接去访问,而是通过"中间人"来帮你完成,这个"中间人"可以做一些增强处理,比如权限检查、记录日志、懒加载等。

使用场景列举

  • 控制对象访问权限(如防火墙代理)

  • 添加额外功能(如记录日志、监控耗时)

  • 延迟加载(如大型图片加载)

  • RPC 框架远程调用(如 Dubbo)

  • Spring AOP 实现原理

代理模式UML图示

综合静态代理、JDK 代理、CGLIB代理、Spring AOP代理如下:

本篇将基于此图,综合分析并代码示例改图的代理模式.

二、静态代理(Static Proxy)

你想去看一场电影,但自己太忙,于是打电话让朋友帮你买:

"小张,帮我买张电影票,如果时间太晚就别买了。"

朋友(代理)可以在你不知道的情况下,帮你加上"时间检查"的功能 ------ 这就是静态代理的"前后增强"。

1. 定义接口

java 复制代码
// 电影票服务接口(被代理的核心功能)
public interface TicketService {
    void buyTicket();
}

2. 真实对象

java 复制代码
// 真实用户类(实际执行买票操作)
public class User implements TicketService {
    @Override
    public void buyTicket() {
        System.out.println("🎟️ 成功购买电影票!");
    }
}

3. 代理对象

java 复制代码
import java.time.LocalTime;

// 朋友代理类(添加时间检查功能)
public class FriendProxy implements TicketService {
    private final TicketService realUser; // 持有真实对象的引用

    public FriendProxy(TicketService realUser) {
        this.realUser = realUser;
    }

    @Override
    public void buyTicket() {
        // 前增强:时间检查
        if (checkTime()) {
            System.out.println("🕒 当前时间允许购票");
            realUser.buyTicket(); // 调用真实对象的方法
        } else {
            System.out.println("⏰ 当前时间已晚,停止购票");
        }
    }

    // 私有方法实现具体增强逻辑
    private boolean checkTime() {
        LocalTime now = LocalTime.now();
        return !now.isAfter(LocalTime.of(22, 0)); // 22点后禁止购票
    }
}

4. 客户端调用

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        TicketService user = new User();
        
        // 创建代理对象(包装真实对象)
        TicketService friendProxy = new FriendProxy(user);
        
        System.out.println("=== 场景1:21:30购票 ===");
        setTestTime(21, 30);
        friendProxy.buyTicket();

        System.out.println("\n=== 场景2:22:30购票 ===");
        setTestTime(22, 30);
        friendProxy.buyTicket();
    }

    // 测试辅助方法:模拟设置时间
    private static void setTestTime(int hour, int minute) {
        // 实际开发中应使用真实时间,此处仅为演示
        System.out.println("🕒 模拟系统时间:" + 
            String.format("%02d:%02d", hour, minute));
    }
}

特点总结

  • 编译期就确定代理类

  • 代理类与真实类实现同一接口

  • 缺点:为每个目标类写一个代理类,代码臃肿

三、动态代理(JDK Proxy)

代购平台不关心你具体买什么票,只提供"通用服务":你输入需求,它自动派人帮你处理,甚至可以"插入"下单前的优惠券、付款提醒等逻辑。这类平台通过"接口"来适配不同的商品、服务 ------ 正如 JDK 动态代理一样:通过接口适配多个真实对象,运行时生成代理对象

步骤:

1. 定义通用服务接口

java 复制代码
// 通用购买服务接口(动态代理的核心适配点)
public interface PurchaseService {
    void buy(String item);
}

2. 创建多个真实服务实现

java 复制代码
// 电影票购买服务
public class TicketService implements PurchaseService {
    @Override
    public void buy(String item) {
        System.out.println("🎟️ 正在购买 " + item);
    }
}

// 图书购买服务
public class BookService implements PurchaseService {
    @Override
    public void buy(String item) {
        System.out.println("📚 正在购买 " + item);
    }
}

3. 创建动态代理处理器(核心增强逻辑)

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PurchaseProxyHandler implements InvocationHandler {
    private final Object target; // 动态绑定的真实对象

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前增强:优惠券检查
        checkCoupon();
        
        // 执行真实对象的方法
        Object result = method.invoke(target, args);
        
        // 后增强:付款提醒
        paymentReminder(method.getName());
        
        return result;
    }

    private void checkCoupon() {
        System.out.println("🎟️ 检查可用优惠券...");
        // 实际开发中可接入优惠券系统
        System.out.println("✅ 发现满50减10优惠券,已自动应用!");
    }

    private void paymentReminder(String methodName) {
        System.out.println("💳 即将发起支付,请确保账户余额充足");
        System.out.println("🔔 交易提醒:" + methodName + " 操作已完成");
    }
}

4.创建代理工厂(运行时生成代理)

java 复制代码
import java.lang.reflect.Proxy;

public class ProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new PurchaseProxyHandler(target)
        );
    }
}

5. 客户端调用演示

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        PurchaseService ticketService = new TicketService();
        PurchaseService bookService = new BookService();

        // 动态生成代理对象(运行时绑定)
        PurchaseService ticketProxy = ProxyFactory.createProxy(ticketService);
        PurchaseService bookProxy = ProxyFactory.createProxy(bookService);

        System.out.println("=== 场景1:购买电影票 ===");
        ticketProxy.buy("《速度与激情10》电影票");

        System.out.println("\n=== 场景2:购买技术书籍 ===");
        bookProxy.buy("《Java核心编程》");
    }
}

6.运行结果演示

四、CGLIB 动态代理(继承方式)

你弟弟长得像你,让他冒充你去电影院排队买票。他可以复用你的一切行为,还能做一些你平时不做的事,比如买爆米花顺手打卡。这是 CGLIB 动态代理 ------ 不依赖接口,而是继承原类进行增强

JDK 动态代理只能代理接口,而 CGLIB 可以代理没有接口的类,通过继承方式实现。

1. 添加CGLIB依赖(Maven配置)

xml 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2. 创建目标类(无接口)

java 复制代码
// 真实用户类(无需实现任何接口)
public class User {
    public void buyTicket() {
        System.out.println("🎟️ 正在使用本人身份购买电影票");
    }
 
    private void secretAction() {
        System.out.println("(这是只有本人能做的私密操作)");
    }
}

3. 创建CGLIB代理处理器

java 复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
 
public class CglibProxyHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        // 前增强:冒充检查
        if (checkImpersonation()) {
            System.out.println("🎭 弟弟正在冒充身份...");
            
            // 执行目标方法(通过MethodProxy保证子类调用)
            Object result = proxy.invokeSuper(obj, args);
            
            // 后增强:附加操作
            buySnacks();
            clockIn();
            
            return result;
        }
        return null;
    }
 
    private boolean checkImpersonation() {
        System.out.println("🔍 验证身份信息...");
        // 实际开发中可接入人脸识别等验证逻辑
        return true;
    }
 
    private void buySnacks() {
        System.out.println("🍿 顺手购买大份爆米花");
    }
 
    private void clockIn() {
        System.out.println("📍 完成影院打卡任务");
    }
}

4. 创建代理工厂

java 复制代码
import net.sf.cglib.proxy.Enhancer;
 
public class CglibProxyFactory {
    public static <T> T createProxy(Class<T> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass); // 设置目标类为父类
        enhancer.setCallback(new CglibProxyHandler());
        return (T) enhancer.create();
    }
}

5. 客户端调用演示

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建原始对象
        User realUser = new User();
        
        // 动态生成代理对象(继承自User)
        User proxyUser = CglibProxyFactory.createProxy(User.class);
 
        System.out.println("=== 场景1:正常购票 ===");
        proxyUser.buyTicket();
 
        System.out.println("\n=== 场景2:尝试调用私密方法 ===");
        try {
            proxyUser.secretAction();
        } catch (Exception e) {
            System.out.println("❗ 代理无法访问私有方法:" + e.getMessage());
        }
    }
}

6.运行结果演示

五、JDK vs CGLIB 区别对比

特性 JDK 动态代理 CGLIB 动态代理
代理方式 接口 继承类
是否要求实现接口
性能 JDK 在 Java8 之后更优 CGLIB 在方法调用多时更优
生成原理 Proxy + 反射 ASM字节码增强
Spring AOP 默认 接口用 JDK,否则用 CGLIB 接口优先使用 JDK

六、Spring AOP 基于代理实现

你佩戴了一副智能眼镜,它会在你每次看片前自动记录日志、在过长时间时提示你休息,还能阻止你观看某些类型的影片。这正是 Spring AOP 的理念 ------ 在不改变原始业务逻辑的前提下,通过代理注入增强功能

1. 创建核心业务接口与实现

java 复制代码
// 影片服务接口
public interface MovieService {
    void watchMovie(String movieType);
}
 
// 用户观影实现类
@Component
public class UserMovieService implements MovieService {
    @Override
    public void watchMovie(String movieType) {
        System.out.println("🎬 正在观看《黑客帝国》 - 类型:" + movieType);
    }
}

2. 定义AOP增强切面

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
 
@Aspect
@Component
public class SmartGlassesAspect {
 
    // 前置通知:观看日志记录
    @Before("execution(* com.example.service.MovieService.watchMovie(..))")
    public void logWatchStart() {
        System.out.println("📅 [" + java.time.LocalTime.now() + "] 开始观影记录");
    }
 
    // 后置通知:健康提醒
    @After("execution(* com.example.service.MovieService.watchMovie(..))")
    public void healthReminder() {
        System.out.println("👁️ 持续观看45分钟,建议远眺休息!");
    }
 
    // 环绕通知:内容过滤(核心增强)
    @Around("execution(* com.example.service.MovieService.watchMovie(..)) && args(movieType)")
    public Object contentFilter(ProceedingJoinPoint joinPoint, String movieType) throws Throwable {
        // 类型检查
        if (isRestrictedType(movieType)) {
            System.out.println("⛔ 检测到限制级内容,已阻止播放!");
            throw new SecurityException("内容访问被拒绝");
        }
 
        // 执行原始方法
        System.out.println("🔍 智能眼镜正在进行内容安全扫描...");
        Object result = joinPoint.proceed();
 
        // 播放后处理
        System.out.println("🎥 影片播放完毕,自动记录观看历史");
        return result;
    }
 
    private boolean isRestrictedType(String type) {
        return "horror".equalsIgnoreCase(type) || "adult".equalsIgnoreCase(type);
    }
}

3. Spring配置类(启用AOP)

java 复制代码
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy // 关键注解:启用AOP自动代理
public class AppConfig {
}

4. 客户端测试类

java 复制代码
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class Client {
    public static void main(String[] args) {
        // 初始化Spring容器
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext(AppConfig.class);
 
        // 获取代理对象(JDK/CGLIB自动选择)
        MovieService movieService = context.getBean(MovieService.class);
 
        System.out.println("=== 场景1:正常观影 ===");
        movieService.watchMovie("sci-fi");
 
        System.out.println("\n=== 场景2:观看限制级内容 ===");
        try {
            movieService.watchMovie("horror");
        } catch (Exception e) {
            System.out.println("❗ 错误处理:" + e.getMessage());
        }
 
        context.close();
    }
}

5.运行结果演示

七、实际开发中常见用途

用途 示例
AOP 日志、事务、权限
RPC 框架 Dubbo、gRPC
缓存代理 加入缓存处理逻辑
安全代理 权限校验、输入检查
延迟加载 图片、数据库懒加载

八、代理模式的优缺点

优点

  • 职责清晰,控制访问

  • 可插拔增强逻辑(如日志、缓存)

  • 解耦核心逻辑与扩展逻辑

缺点

  • 多层代理可能调试困难

  • 代码复杂性上升

  • 动态代理性能稍低(尤其频繁调用场景)

九、总结

Java 中的代理模式是一种强大而灵活的设计模式,通过代理对象来增强、控制或简化对象行为。掌握代理模式,特别是静态代理、JDK 动态代理、CGLIB 动态代理,不仅有助于理解 AOP、RPC 等技术的实现原理,也能帮助我们在日常编码中更好地组织和解耦代码逻辑。最后,用类比和思维导图来帮助记忆:
类比

  • 【静态代理】:请朋友帮你办事,还能顺带提醒你别忘带身份证。

  • 【JDK 代理】:用淘宝代购平台下单,平台接口决定代理谁执行。

  • 【CGLIB】:让弟弟假扮你去领快递,他还带回了饮料。

  • 【Spring AOP】:你佩戴智能眼镜看电影,自动记录与控制行为。
    总结

以下是代理模式相关信息的表格化呈现:

模块分类 子分类 详细说明
代理模式 分类 静态代理、动态代理(JDK/CGLIB)
实现方式 接口实现(JDK/静态代理)、类继承(CGLIB)
核心应用场景 AOP(日志/事务/安全)、RPC框架、缓存/权限控制、懒加载优化
Spring框架集成 AOP实现方式 @Aspect注解驱动切面、ProxyFactoryBean代理工厂
特性对比 优点 无侵入增强、业务解耦、细粒度控制、代码复用
缺点 调试复杂度增加、代理开销、学习曲线陡峭、对final类/方法限制(CGLIB)

其他

  1. 动态代理分支:

    • JDK动态代理:基于接口实现,通过InvocationHandler拦截
    • CGLIB动态代理:基于类继承实现,通过MethodInterceptor拦截,可代理无接口类
  2. AOP典型应用场景:

    • 分布式事务管理
    • 方法执行时间监控
    • 自定义权限校验
    • 接口调用日志审计
  3. 代理模式演进:

    静态代理 → JDK动态代理 → CGLIB → ASM字节码增强 → 编译期注解处理(如Lombok)

十、参考

《23种设计模式概览》

附本篇图语言:

@startuml

' 定义代理模式主类

class ProxyPattern {

+{static} main()

}

' 代理分类

package "代理分类" #DDDDDD {

class StaticProxy {

+invoke()

}

package "动态代理" #CCCCCC {

class JDKDynamicProxy {

+invoke()

}

复制代码
class CGLIBProxy {
  +intercept()
}

}

}

' 实现方式

package "实现方式" #FFAAAA {

interface TargetInterface {

+request()

}

class RealSubject {

+request()

}

class ProxySubject {

-realSubject: RealSubject

+request()

}

}

' 应用场景

package "应用场景" #AAFFAA {

class AOP {

+logging()

+transaction()

}

class RPC {

+remoteCall()

}

class CacheProxy {

+getData()

}

}

' Spring集成

package "Spring集成" #AAAAFF {

class AspectJ {

+@Before()

+@After()

}

class ProxyFactoryBean {

-target

-interceptorNames

}

}

' 关系定义

ProxyPattern --> StaticProxy : 使用

ProxyPattern --> JDKDynamicProxy : 使用

ProxyPattern --> CGLIBProxy : 使用

RealSubject ...|> TargetInterface : 实现

ProxySubject ...|> TargetInterface : 实现

ProxySubject o-- RealSubject : 委托

AOP --> ProxyPattern : 应用场景

RPC --> ProxyPattern : 应用场景

CacheProxy --> ProxyPattern : 应用场景

AspectJ --> ProxyPattern : 实现方式

ProxyFactoryBean --> ProxyPattern : 实现方式

' 优缺点标注

note right of ProxyPattern

优点:

  • 解耦调用方与实现方
  • 统一添加横切逻辑
  • 控制对象访问

缺点:

  • 增加系统复杂度
  • 调试难度提升
  • 性能轻微损耗
    end note

@enduml

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔7 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说12 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json