当前位置: 首页 > news >正文

Java 编程之代理模式

前言

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

一、什么是代理模式?

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

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

使用场景列举

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

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

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

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

  • Spring AOP 实现原理

代理模式UML图示

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

在这里插入图片描述
本篇将基于此图,综合分析并代码示例改图的代理模式.

二、静态代理(Static Proxy)

你想去看一场电影,但自己太忙,于是打电话让朋友帮你买:
“小张,帮我买张电影票,如果时间太晚就别买了。”
朋友(代理)可以在你不知道的情况下,帮你加上“时间检查”的功能 —— 这就是静态代理的“前后增强”。

1. 定义接口

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

2. 真实对象

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

3. 代理对象

import java.time.LocalTime;// 朋友代理类(添加时间检查功能)
public class FriendProxy implements TicketService {private final TicketService realUser; // 持有真实对象的引用public FriendProxy(TicketService realUser) {this.realUser = realUser;}@Overridepublic 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. 客户端调用

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. 定义通用服务接口

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

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

// 电影票购买服务
public class TicketService implements PurchaseService {@Overridepublic void buy(String item) {System.out.println("🎟️ 正在购买 " + item);}
}// 图书购买服务
public class BookService implements PurchaseService {@Overridepublic void buy(String item) {System.out.println("📚 正在购买 " + item);}
}

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

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;}@Overridepublic 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.创建代理工厂(运行时生成代理)

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. 客户端调用演示

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配置)

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

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

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

3. 创建CGLIB代理处理器

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CglibProxyHandler implements MethodInterceptor {@Overridepublic 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. 创建代理工厂

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. 客户端调用演示

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. 创建核心业务接口与实现

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

2. 定义AOP增强切面

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)

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. 客户端测试类

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种设计模式概览》
在这里插入图片描述

相关文章:

  • Matter协议开发者指南:使用Matter SDK构建智能家居应用
  • 数学公式排版简史
  • JavaWeb-day1
  • Spring Cloud 服务调用详解:Ribbon 负载均衡与 Feign 声明式接口调用
  • 第一章:认识AI
  • vtk和opencv和opengl直接的区别是什么?
  • JSON 数据格式详解
  • 【Java项目设计】基于Springboot+Vue的OA办公自动化系统
  • idea有了!多尺度时间序列新SOTA!
  • 前端基础知识CSS系列 - 16(css视差滚动效果)
  • OSPF 路由协议基础实验
  • 每天一个前端小知识 Day 7 - 现代前端工程化与构建工具体系
  • 如何理解Java反射机制
  • 【第二章:机器学习与神经网络概述】02.降维算法理论与实践-(2)线性判别分析(Linear Discriminant Analysis, LDA)
  • AbMole明星分子 |Acetylcysteine:从细胞保护到动物研究的全应用
  • flask静态资源与模板页面、模板用户登录案例
  • leetcode hot100 两数之和
  • GitHub Actions + SSH 自动部署教程
  • aws(学习笔记第四十五课) route53-failover
  • Arcgis地理配准变换方法说明
  • 做博彩 网站违法吗/百度sem认证
  • 企业网站建设心得/乐云seo
  • 郑州做网站公司yooker/网络优化工程师简历
  • 必须做网站等级保护/站长之家综合查询工具
  • 专门做照片书的网站/seochinazcom
  • 做百度网站哪家公司好/深圳seo优化服务