java:代理类、静态代理、动态代理
一、为什么需要代理类?
想象一个现实生活中的场景:你是一个明星,有很多商业活动和采访邀约。但你不会自己去处理所有琐事(比如谈合同、安排行程、应付记者),而是聘请一位经纪人。
-
你(明星):核心人物,负责最核心的工作(上台表演、接受采访)。
-
经纪人:代替你处理一切前后期工作。他在你工作之前(筛选活动、谈价钱)、之后(收钱、处理舆情)做一些额外的事情,但他最终的目的还是让你能顺利工作。
在程序中,代理模式就是为其他对象(“明星”)提供一个代理(“经纪人”)以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。
为什么要这样做?
-
职责清晰:让“明星”只关注核心业务,其他公共逻辑(如日志、事务、安全)交给“经纪人”处理。
-
功能增强:在不修改“明星”原有代码的情况下,“经纪人”可以额外添加新的功能(如记录活动日志),这符合 “开放-封闭原则”。
-
控制访问:“经纪人”可以决定是否将请求转发给“明星”,例如进行权限校验。
二、Java 中代理类的实现方式
Java 主要提供了两种代理实现方式:
-
静态代理:在编译期就确定代理关系,程序员手动创建代理类。
-
动态代理:在运行时动态生成代理类,非常灵活。
三、场景与代码说明
场景:为一个服务方法添加性能监控
假设我们有一个核心服务接口 UserService,我们想在执行其方法前后记录耗时,但又不想修改原有的业务代码。
1. 定义接口和真实对象(明星)
// 1. 服务接口 (明星的工作规范)
public interface UserService {
void addUser();
void deleteUser();
}
// 2. 真实对象,实现接口 (真正的明星)
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
// 模拟核心业务逻辑
System.out.println("添加用户...");
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void deleteUser() {
// 模拟核心业务逻辑
System.out.println("删除用户...");
try {
Thread.sleep(200); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. 静态代理 (手动聘请的经纪人)
// 静态代理类,也必须实现同一个接口
public class UserServiceStaticProxy implements UserService {
// 持有真实对象的引用
private UserService target;
public UserServiceStaticProxy(UserService target) {
this.target = target;
}
@Override
public void addUser() {
// 【前置增强】- 经纪人的工作
long startTime = System.currentTimeMillis();
System.out.println("记录日志:开始执行 addUser()");
// 【核心业务】- 让明星工作
target.addUser();
// 【后置增强】- 经纪人的工作
long endTime = System.currentTimeMillis();
System.out.println("记录日志:addUser() 执行完成,耗时:" + (endTime - startTime) + "ms");
}
@Override
public void deleteUser() {
// 同样的模式...
long startTime = System.currentTimeMillis();
System.out.println("记录日志:开始执行 deleteUser()");
target.deleteUser();
long endTime = System.currentTimeMillis();
System.out.println("记录日志:deleteUser() 执行完成,耗时:" + (endTime - startTime) + "ms");
}
}
使用静态代理:
public class StaticProxyDemo {
public static void main(String[] args) {
// 创建真实对象(明星)
UserService realStar = new UserServiceImpl();
// 创建代理对象(经纪人),并让经纪人知道为谁工作
UserService agent = new UserServiceStaticProxy(realStar);
// 客户端和经纪人打交道,感觉不到明星的存在
agent.addUser();
System.out.println("----------");
agent.deleteUser();
}
}
输出结果:
text
记录日志:开始执行 addUser() 添加用户... 记录日志:addUser() 执行完成,耗时:100ms ---------- 记录日志:开始执行 deleteUser() 删除用户... 记录日志:deleteUser() 执行完成,耗时:200ms
静态代理的缺点:
-
如果接口有很多方法,代理类会变得非常臃肿,每个方法都要写重复的增强代码。
-
如果有很多类需要代理,就需要为每一个类都创建一个代理类,难以维护。
3. 动态代理 (一个万能经纪人)
JDK 动态代理利用了反射机制,在程序运行时动态地创建代理类。它有一个“万能经纪人”,可以代理任何实现了接口的“明星”。
创建一个“万能经纪人”(调用处理器):
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 万能经纪人,实现 InvocationHandler 接口
public class PerformanceMonitorHandler implements InvocationHandler {
// 持有真实对象(任何明星都可以)
private Object target;
public PerformanceMonitorHandler(Object target) {
this.target = target;
}
/**
* 所有通过代理对象调用的方法,都会自动走到这个 invoke 方法里来
* @param proxy 代理对象本身(通常很少用)
* @param method 当前被调用的方法(addUser, deleteUser...)
* @param args 调用方法时传入的参数
* @return 原方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 【前置增强】
long startTime = System.currentTimeMillis();
System.out.println("记录日志:开始执行 " + method.getName() + "()");
// 【核心业务】:利用反射机制,调用真实对象的方法
Object result = method.invoke(target, args);
// 【后置增强】
long endTime = System.currentTimeMillis();
System.out.println("记录日志:" + method.getName() + "() 执行完成,耗时:" + (endTime - startTime) + "ms");
return result; // 返回原方法执行的结果
}
}
使用动态代理:
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
// 1. 创建真实对象(明星)
UserService realStar = new UserServiceImpl();
// 2. 创建“万能经纪人”,并告诉他代理谁
InvocationHandler handler = new PerformanceMonitorHandler(realStar);
// 3. 使用Proxy类的工厂方法,动态地创建一个代理对象
// 参数:类加载器、代理类要实现的接口、调用处理器
UserService agent = (UserService) Proxy.newProxyInstance(
realStar.getClass().getClassLoader(), // 用明星的类加载器
realStar.getClass().getInterfaces(), // 明星实现的所有接口(这样经纪人才有同样能力)
handler // 我们的万能经纪人
);
// 4. 使用代理对象
// 注意:这里的 agent 不是 UserServiceStaticProxy 的实例,而是在运行时动态生成的一个类$Proxy0的实例
agent.addUser();
System.out.println("----------");
agent.deleteUser();
// 可以看下代理对象的类名
System.out.println("代理对象的类型:" + agent.getClass().getName());
}
}
输出结果(与静态代理一致):
记录日志:开始执行 addUser() 添加用户...
记录日志:addUser() 执行完成,耗时:100ms ----------
记录日志:开始执行 deleteUser() 删除用户...
记录日志:deleteUser() 执行完成,耗时:200ms 代理对象的类型:com.sun.proxy.$Proxy0 // 这是一个动态生成的类
动态代理的优点:
-
通用性强:一个
InvocationHandler可以代理所有类型的对象,只要它有接口。 -
易于维护:不需要为每个类都创建代理类,避免代码冗余。
四、总结与对比
| 特性 | 静态代理 | 动态代理 |
|---|---|---|
| 实现方式 | 程序员手动编写代理类 | JDK 运行时动态生成代理类 |
| 灵活性 | 差,一个代理类只能代理一种类型 | 好,一个处理类可代理多种类型 |
| 可维护性 | 差,接口改动时代理类也需修改 | 好,逻辑集中在一个处理类中 |
| 技术要求 | 低,简单易懂 | 高,需要理解反射机制 |
| 应用场景 | 代理类较少、方法较简单时 | 主流应用,如Spring AOP、RPC框架 |
为什么需要代理?—— 再次总结
-
解耦与非侵入性:将核心业务逻辑(如添加用户)和横切关注点(如日志、事务、安全)分离。你可以增强一个类的功能而无需修改其代码。
-
复用公共逻辑:像日志、性能监控这样的代码,通过代理可以写一次,然后应用到任何需要的地方。
-
权限控制:在代理中,可以方便地加入权限判断逻辑,决定是否调用真实对象的方法。
现实世界的应用:
-
Spring AOP:其底层就是基于动态代理来实现的面向切面编程,用于处理事务(
@Transactional)、日志、安全等。 -
RPC框架:远程服务调用(如Dubbo)中,客户端拿到的“服务”实际上是一个本地代理,它负责将方法调用封装成网络请求并发送给远端的服务器。
-
MyBatis:Mapper接口没有实现类,但MyBatis通过动态代理为你生成了实现类,代理方法会执行SQL并返回结果。
更多推荐

所有评论(0)