Java案例中的代理模式怎么用?一文讲透原理、场景与实战代码
目录导读
- 代理模式是什么?核心思想一句话讲清
- 代理模式的三大角色与UML图解
- Java代理的四种实现方式对比
- 静态代理实现日志记录(最易懂的入门)
- 动态代理JDK Proxy实现权限校验
- CGLIB代理实现无接口类的延迟加载
- 代理模式在实际框架中的应用(Spring AOP / MyBatis)
- 常见问题Q&A(面试高频)
- 什么时候该用代理模式?
代理模式是什么?核心思想一句话讲清
代理模式(Proxy Pattern) 是一种结构型设计模式,它允许你提供一个代理对象来控制对另一个对象的访问。
通俗地说:你不想直接访问“本人”,于是找了一个“代理人”来替你完成某些操作,并在操作前后附加额外逻辑。

举个生活案例:明星的经纪人就是代理——粉丝要找明星演出,必须先通过经纪人谈档期、谈费用,经纪人还可以在签约前后做宣传、收尾款,而明星只负责上台表演。
在Java中,代理模式常用于:日志记录、权限控制、延迟加载、远程调用、事务管理等场景。
代理模式的三大角色与UML图解
代理模式包含三个核心角色:
| 角色 | 定义 | 例子 |
|---|---|---|
| Subject(主题接口) | 定义真实对象和代理对象共同的接口 | Star 接口 |
| RealSubject(真实主题) | 真正执行业务逻辑的对象 | RealStar |
| Proxy(代理) | 持有真实对象的引用,控制对它的访问,并添加额外操作 | AgentProxy |
┌─────────────┐ implements ┌───────────────┐
│ Subject │◄─────────────────────│ Proxy │
│ (接口) │ │ -RealSubject │
│ +operation() │ │ +operation() │
└─────────────┘ └───────┬───────┘
│
┌────────▼───────┐
│ RealSubject │
│ +operation() │
└────────────────┘
Java代理的四种实现方式对比
| 实现方式 | 是否需要接口 | 原理 | 性能 | 适用场景 |
|---|---|---|---|---|
| 静态代理 | 需要接口 | 手动编写代理类 | 较快 | 代理类较少且固定 |
| JDK动态代理 | 需要接口 | 运行时生成代理对象 | 快 | 面向接口编程 |
| CGLIB动态代理 | 不需要接口 | 生成子类覆盖方法 | 较快(低版本略慢) | 无接口类 |
| 虚拟代理/远程代理 | 视情况 | 内部逻辑控制 | 取决于实现 | 延迟加载、RMI |
案例一:静态代理实现日志记录(最易懂的入门)
场景:现有UserService接口,需要为每个方法添加执行前后日志。
// 1. 主题接口
public interface UserService {
void addUser(String name);
void deleteUser(int id);
}
// 2. 真实主题
public class UserServiceImpl implements UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
public void deleteUser(int id) {
System.out.println("删除用户ID: " + id);
}
}
// 3. 静态代理类
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
public void addUser(String name) {
System.out.println("[日志] 开始执行addUser, 参数: " + name);
target.addUser(name);
System.out.println("[日志] addUser执行结束");
}
public void deleteUser(int id) {
System.out.println("[日志] 开始执行deleteUser, 参数: " + id);
target.deleteUser(id);
System.out.println("[日志] deleteUser执行结束");
}
}
// 4. 客户端调用
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userService);
proxy.addUser("张三");
proxy.deleteUser(1);
}
}
输出:
[日志] 开始执行addUser, 参数: 张三
添加用户: 张三
[日志] addUser执行结束
[日志] 开始执行deleteUser, 参数: 1
删除用户ID: 1
[日志] deleteUser执行结束
优点:简单直观,不依赖第三方库。
缺点:每个真实类都需要手动编写一个代理类,当业务类很多时代码冗余严重。
案例二:动态代理JDK Proxy实现权限校验
场景:为Calculator接口的所有方法添加权限校验(只有admin用户能调用)。
// 1. 主题接口
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
// 2. 真实主题
public class CalculatorImpl implements Calculator {
public int add(int a, int b) { return a + b; }
public int subtract(int a, int b) { return a - b; }
}
// 3. 调用处理器(代理逻辑)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SecurityProxyHandler implements InvocationHandler {
private Object target;
private String currentUser;
public SecurityProxyHandler(Object target, String currentUser) {
this.target = target;
this.currentUser = currentUser;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"admin".equals(currentUser)) {
throw new SecurityException("权限不足,只有admin用户可以执行操作");
}
System.out.println("[权限校验] 用户 " + currentUser + " 通过校验");
Object result = method.invoke(target, args);
return result;
}
// 工厂方法创建代理对象
public static Object createProxy(Object target, String user) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new SecurityProxyHandler(target, user)
);
}
}
// 4. 客户端
public class Client {
public static void main(String[] args) {
Calculator realCalc = new CalculatorImpl();
// 用普通用户尝试
Calculator proxy1 = (Calculator) SecurityProxyHandler.createProxy(realCalc, "guest");
try {
proxy1.add(3, 5);
} catch (SecurityException e) {
System.out.println(e.getMessage());
}
// 用admin用户
Calculator proxy2 = (Calculator) SecurityProxyHandler.createProxy(realCalc, "admin");
System.out.println("结果: " + proxy2.add(3, 5));
}
}
输出:
权限不足,只有admin用户可以执行操作
[权限校验] 用户 admin 通过校验
结果: 8
核心机制:Proxy.newProxyInstance 在运行时动态生成一个实现了Calculator接口的代理类,所有方法调用都会转发到invoke()方法中。
注意:JDK动态代理要求目标类必须实现接口。
案例三:CGLIB代理实现无接口类的延迟加载
场景:一个大型对象HeavyObject的创建非常耗时,希望在使用时才真正初始化。
// 1. 需要被代理的类(无接口)
public class HeavyObject {
public HeavyObject() {
System.out.println("HeavyObject 正在初始化... (消耗大量资源)");
// 模拟耗时操作
try { Thread.sleep(2000); } catch (InterruptedException e) { }
}
public void doWork() {
System.out.println("开始执行实际业务...");
}
}
// 2. CGLIB延迟加载代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LazyProxyFactory {
public static Object createLazyProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new LazyInterceptor());
return enhancer.create();
}
private static class LazyInterceptor implements MethodInterceptor {
private Object realObject = null;
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
if (realObject == null) {
realObject = targetClass.newInstance(); // 首次真正调用时创建
}
return method.invoke(realObject, args);
}
}
}
// 3. 客户端
public class Client {
public static void main(String[] args) {
HeavyObject proxy = (HeavyObject) LazyProxyFactory.createLazyProxy(HeavyObject.class);
System.out.println("代理对象创建完成,但尚未初始化真实对象");
// 直到调用方法才初始化
proxy.doWork(); // 此时才输出 "HeavyObject 正在初始化..."
}
}
输出:
代理对象创建完成,但尚未初始化真实对象
HeavyObject 正在初始化... (消耗大量资源)
开始执行实际业务...
优势:不要求被代理类实现接口,通过继承生成子类代理。
注意:CGLIB不能代理final类或final方法。
代理模式在实际框架中的应用
1 Spring AOP 的核心实现就是动态代理
Spring AOP(面向切面编程)底层使用JDK动态代理或CGLIB代理:
- 如果目标对象实现了接口,默认使用JDK动态代理
- 如果目标对象没有实现接口,则使用CGLIB代理
// Spring AOP 声明式事务管理示例
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 这里的@Transactional 就是通过代理在方法前后添加了开启事务、提交/回滚的逻辑
}
代理做的事情:
- 方法前:
beginTransaction() - 方法后:
commit()或rollback() - 异常时:
rollback()
2 MyBatis Mapper接口的代理
MyBatis中,我们定义的Mapper接口实际上没有实现类,MyBatis框架通过JDK动态代理为每个Mapper接口生成代理对象:
// 写一个接口,不需要实现类
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(int id);
}
// 使用时
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findById(1); // 实际调用了代理对象的invoke()
代理内部逻辑:将方法调用解析为SQL语句,执行数据库查询,将结果集映射为对象。
常见问题Q&A(面试高频)
Q1:静态代理和动态代理的核心区别是什么?
A:静态代理是在编译期手动编写代理类,一个代理类只能代理一个接口;动态代理是在运行期由JVM自动生成代理类,一个处理器可以代理任意接口。
Q2:JDK动态代理为什么必须要求目标类实现接口?
A:因为JDK动态代理生成的代理类默认继承Proxy类(Java单继承限制),只能通过实现目标接口的方式来拥有与目标类相同的方法签名,如果目标类没有接口,则无法使用JDK代理。
Q3:CGLIB能代理final类或final方法吗?
A:不能,CGLIB通过生成子类来代理,final类不能被继承,final方法不能被重写,因此无法代理。
Q4:什么时候应该选择JDK动态代理 vs CGLIB?
A:优先选择JDK动态代理(因为它由Java原生支持,性能稳定),只有当目标类没有实现任何接口时,才使用CGLIB,在Spring中,默认会智能切换。
Q5:代理模式和装饰器模式有什么区别?
A:二者结构类似,但意图不同:
- 代理模式:控制对目标对象的访问(权限、延迟加载、远程等)
- 装饰器模式:动态增强目标对象的功能(如给咖啡加糖、加奶)
通俗理解:代理是“我替你做,但最后还得你做”;装饰器是“我帮着你一起做更多”。
什么时候该用代理模式?
| 场景 | 推荐代理类型 |
|---|---|
| 需要为多个接口方法添加统一日志、监控、异常处理 | JDK动态代理 |
| 需要缓存/延迟加载大型对象 | CGLIB延迟代理 |
| 需要对无接口的第三方类进行功能增强 | CGLIB |
| 需要实现远程对象调用(RPC/RMI) | 远程代理 |
| 需要控制对敏感对象的访问(权限、安全) | 动态代理或静态代理 |
一句话选择原则:
有接口用JDK动态代理,无接口用CGLIB动态代理;
代理类少且不常变时可用静态代理,但大多数现代项目直接上动态代理更灵活。
写在最后
代理模式是Java中应用最广泛的设计模式之一,从Spring AOP到MyBatis,从Hibernate延迟加载到RPC框架,处处可见它的身影。
理解并掌握静态代理 → JDK动态代理 → CGLIB代理的演进过程,能让你的代码更具扩展性。
下次面试官问“代理模式怎么用”,你不仅能说出理论,还能给出三个完整案例——这,就是你的核心竞争力。
记住:代理不创造新功能,它只让你原本的代码“被看到”之前,有机会做点额外的事。