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) |
其他:
-
动态代理分支:
- JDK动态代理:基于接口实现,通过
InvocationHandler
拦截 - CGLIB动态代理:基于类继承实现,通过
MethodInterceptor
拦截,可代理无接口类
- JDK动态代理:基于接口实现,通过
-
AOP典型应用场景:
- 分布式事务管理
- 方法执行时间监控
- 自定义权限校验
- 接口调用日志审计
-
代理模式演进:
静态代理 → JDK动态代理 → CGLIB → ASM字节码增强 → 编译期注解处理(如Lombok)
十、参考
《23种设计模式概览》