前言
代理模式(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 {
@Override
public 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;
}
@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. 客户端调用
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 {
@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. 创建动态代理处理器(核心增强逻辑)
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.创建代理工厂(运行时生成代理)
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 {
@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. 创建代理工厂
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 {
@Override
public 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)