代理模式是 Java 中一种常见的设计模式,它通过引入一个代理对象来控制对目标对象的访问,从而在不修改目标对象代码的前提下,增强其功能或控制其访问方式。
一、代理模式的定义与结构
定义:为其他对象提供一种代理以控制对这个对象的访问。
角色:
- 抽象主题(Subject) :定义真实主题和代理的共同接口。
- 真实主题(RealSubject) :真正执行业务逻辑的对象。
- 代理(Proxy) :持有真实主题的引用,在调用真实主题前后执行额外操作(如权限校验、日志、延迟加载等)。
代理模式的核心是间接访问,客户端不直接操作真实对象,而是通过代理。
二、静态代理
实现方式
在编译期就确定代理类,代理类与真实类实现相同接口。
示例:
java
// 抽象主题
interface UserService {
void saveUser(String name);
}
// 真实主题
class UserServiceImpl implements UserService {
@Override
public void saveUser(String name) {
System.out.println("保存用户:" + name);
}
}
// 静态代理
class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void saveUser(String name) {
System.out.println("开始事务");
target.saveUser(name);
System.out.println("提交事务");
}
}
// 使用
UserService real = new UserServiceImpl();
UserService proxy = new UserServiceProxy(real);
proxy.saveUser("Alice");
优缺点
- 优点:结构简单,易于理解。
- 缺点:每个代理类只能为一个接口服务,如果接口很多,会产生大量代理类;且需要硬编码代理逻辑。
三、动态代理
动态代理在运行时动态生成代理类,无需手动编写每个代理类。Java 中主要有两种实现方式。
1. JDK 动态代理(基于接口)
- 要求:目标对象必须实现至少一个接口。
- 核心:
java.lang.reflect.Proxy和InvocationHandler。 - 特点:代理类在运行时生成,实现目标接口,所有方法调用都转发给
InvocationHandler的invoke方法。
示例:
java
// 处理器
class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前:" + method.getName());
Object result = method.invoke(target, args);
System.out.println("调用后:" + method.getName());
return result;
}
}
// 生成代理
UserService real = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
real.getClass().getClassLoader(),
real.getClass().getInterfaces(),
new LogHandler(real)
);
proxy.saveUser("Alice");
2. CGLIB 动态代理(基于子类)
- 要求:目标类不能是
final,方法不能是final/static。 - 原理:通过字节码技术生成目标类的子类作为代理,重写父类方法实现拦截。
- 核心:
net.sf.cglib.proxy.Enhancer和MethodInterceptor。
示例(Spring 的 CGLIB 实现):
java
class UserDao {
public void insert() {
System.out.println("插入数据");
}
}
class DaoInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置处理");
return result;
}
}
// 生成代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDao.class);
enhancer.setCallback(new DaoInterceptor());
UserDao proxy = (UserDao) enhancer.create();
proxy.insert();
动态代理的优缺点
- 优点:一个处理器可以为多个目标对象服务,代理类无需手动编写,灵活性强。
- 缺点:JDK 动态代理要求必须有接口;CGLIB 无法代理
final类和方法。
四、代理模式的应用场景
| 场景 | 说明 |
|---|---|
| AOP(面向切面编程) | Spring AOP 使用动态代理实现方法拦截,如事务、日志、性能监控。 |
| 远程调用(RPC) | 如 Dubbo、Feign,客户端通过代理对象隐藏网络通信细节。 |
| 延迟加载 | Hibernate 等 ORM 框架通过代理实现懒加载,访问属性时才触发数据库查询。 |
| 权限控制 | 代理类在调用前检查用户是否有权限执行该方法。 |
| 缓存 | 代理类在调用真实方法前检查缓存,避免重复计算或访问数据库。 |
| 日志与监控 | 统一记录方法调用参数、耗时等信息。 |
五、静态代理 vs 动态代理
| 对比项 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类生成时间 | 编译期 | 运行期 |
| 代码量 | 每个目标类需要一个代理类 | 一个处理器可代理多个类 |
| 灵活性 | 低,接口变更需修改代理类 | 高,无需手动维护代理类 |
| 性能 | 直接调用,略快 | 反射或字节码生成,略慢(可接受) |
| 典型使用 | 简单场景 | Spring AOP、RPC 框架 |
六、总结
代理模式是 Java 中实现功能增强和访问控制的重要手段。静态代理简单直观,但扩展性差;动态代理利用运行时代码生成,成为主流框架(如 Spring)的基础。理解代理模式,不仅有助于掌握框架原理,还能在实际开发中灵活运用,写出更简洁、可维护的代码。