问题
什么是代理?为什么要使用动态代理?
答案
一、什么是代理?
核心概念
代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。
生活中的例子
客户 ──→ 房产中介(代理) ──→ 房东(目标对象)
↑
增强功能:
- 筛选客户
- 带看房子
- 签订合同
- 收取佣金
代理模式的三个角色
// 1. 抽象主题(Subject):定义代理和真实对象的共同接口
interface Subject {
void request();
}
// 2. 真实主题(RealSubject):实际的业务对象
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("执行真实业务逻辑");
}
}
// 3. 代理(Proxy):持有真实对象的引用,控制对真实对象的访问
class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
before(); // 前置增强
realSubject.request(); // 调用真实对象
after(); // 后置增强
}
private void before() {
System.out.println("代理:前置处理");
}
private void after() {
System.out.println("代理:后置处理");
}
}
二、代理的主要作用
1. 功能增强
在不修改原有代码的情况下,增加额外功能:
// 原始功能
public void transfer(String from, String to, double amount) {
// 转账逻辑
}
// 代理增强:添加日志、权限检查、事务管理
public void transfer(String from, String to, double amount) {
log("开始转账"); // 日志
checkPermission(); // 权限检查
beginTransaction(); // 开启事务
try {
realObject.transfer(from, to, amount); // 原始功能
commit(); // 提交事务
} catch (Exception e) {
rollback(); // 回滚事务
}
log("转账完成");
}
2. 控制访问
// 延迟加载(虚拟代理)
class ImageProxy implements Image {
private RealImage realImage;
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // 延迟创建
}
realImage.display();
}
}
// 权限控制(保护代理)
class ProtectedProxy implements Service {
private RealService realService;
private User currentUser;
@Override
public void execute() {
if (currentUser.hasPermission()) {
realService.execute();
} else {
throw new SecurityException("无权限访问");
}
}
}
3. 远程代理
// RPC 远程调用
interface UserService {
User getUser(int id);
}
// 客户端代理
class UserServiceProxy implements UserService {
@Override
public User getUser(int id) {
// 1. 序列化请求
Request request = new Request("getUser", id);
// 2. 网络传输
Response response = httpClient.send(request);
// 3. 反序列化响应
return (User) response.getData();
}
}
// 使用(像调用本地方法一样)
UserService userService = new UserServiceProxy();
User user = userService.getUser(123);
三、静态代理 vs 动态代理
静态代理
定义:在编译期就确定代理类,需要手动编写代理类代码。
示例:
// 接口
interface UserService {
void addUser(String name);
void deleteUser(int id);
}
// 真实对象
class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public void deleteUser(int id) {
System.out.println("删除用户: " + id);
}
}
// 静态代理类(手动编写)
class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(String name) {
System.out.println("[日志] 调用 addUser");
target.addUser(name);
System.out.println("[日志] addUser 完成");
}
@Override
public void deleteUser(int id) {
System.out.println("[日志] 调用 deleteUser");
target.deleteUser(id);
System.out.println("[日志] deleteUser 完成");
}
}
静态代理的问题:
- 代码冗余:每个方法都要写重复的增强逻辑
- 维护困难:接口新增方法,代理类也要修改
- 扩展性差:每个接口都需要单独的代理类
// 如果有 100 个接口,需要写 100 个代理类
class OrderServiceProxy { }
class ProductServiceProxy { }
class PaymentServiceProxy { }
// ...
动态代理
定义:在运行时动态生成代理类,无需手动编写代理类代码。
JDK 动态代理示例:
// 通用的日志处理器
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;
}
}
// 使用(一个 Handler 处理所有接口)
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LogHandler(userService)
);
OrderService orderService = new OrderServiceImpl();
OrderService orderProxy = (OrderService) Proxy.newProxyInstance(
orderService.getClass().getClassLoader(),
orderService.getClass().getInterfaces(),
new LogHandler(orderService) // 复用同一个 Handler
);
四、为什么要使用动态代理?
1. 减少代码冗余
静态代理:
// 需要为每个接口写代理类
class UserServiceProxy { } // 100 行
class OrderServiceProxy { } // 100 行
class ProductServiceProxy { } // 100 行
// 总计:300 行重复代码
动态代理:
// 一个 InvocationHandler 处理所有接口
class LogHandler implements InvocationHandler {
// 20 行代码,适用于所有接口
}
2. 提高可维护性
静态代理:
// 接口新增方法
interface UserService {
void addUser(String name);
void deleteUser(int id);
void updateUser(User user); // 新增方法
}
// 代理类必须同步修改
class UserServiceProxy implements UserService {
// 必须实现新方法
@Override
public void updateUser(User user) {
// 重复的增强逻辑
}
}
动态代理:
// 接口新增方法,无需修改代理代码
class LogHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 自动处理所有方法(包括新增的)
System.out.println("[日志] 调用 " + method.getName());
return method.invoke(target, args);
}
}
3. 灵活的功能组合
// 可以动态组合多个增强功能
Object proxy = target;
// 添加日志
proxy = Proxy.newProxyInstance(..., new LogHandler(proxy));
// 添加性能监控
proxy = Proxy.newProxyInstance(..., new PerformanceHandler(proxy));
// 添加事务管理
proxy = Proxy.newProxyInstance(..., new TransactionHandler(proxy));
// 最终代理对象具备:日志 + 性能监控 + 事务管理
4. 支持 AOP 编程
// Spring AOP 使用动态代理实现切面编程
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前");
Object result = joinPoint.proceed();
System.out.println("方法执行后");
return result;
}
}
// 底层使用动态代理实现
// 无需手动编写代理类
五、动态代理的实际应用场景
1. Spring AOP
// 事务管理
@Transactional
public void transfer(String from, String to, double amount) {
// Spring 使用动态代理自动添加事务管理
}
// 权限控制
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(int id) {
// Spring Security 使用动态代理检查权限
}
2. MyBatis Mapper 接口
// 只定义接口,无需实现类
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(int id);
}
// MyBatis 使用动态代理生成实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(123);
3. RPC 框架(Dubbo、Feign)
// Dubbo 服务调用
@Reference
private UserService userService;
// Feign 远程调用
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable int id);
}
// 底层使用动态代理实现远程调用
4. 日志、监控、缓存
// 统一日志记录
class LogHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
logger.info("调用方法: {}, 参数: {}", method.getName(), args);
Object result = method.invoke(target, args);
logger.info("返回结果: {}", result);
return result;
}
}
// 性能监控
class PerformanceHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + " 耗时: " + (end - start) + "ms");
return result;
}
}
// 缓存
class CacheHandler implements InvocationHandler {
private Map<String, Object> cache = new HashMap<>();
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
String key = method.getName() + Arrays.toString(args);
if (cache.containsKey(key)) {
return cache.get(key); // 返回缓存
}
Object result = method.invoke(target, args);
cache.put(key, result);
return result;
}
}
六、静态代理 vs 动态代理对比
| 对比维度 | 静态代理 | 动态代理 |
|---|---|---|
| 代码量 | 每个接口需要一个代理类 | 一个 Handler 处理所有接口 |
| 维护性 | 接口变化需要同步修改代理类 | 无需修改代理代码 |
| 灵活性 | 编译期确定,不灵活 | 运行时生成,灵活 |
| 性能 | 直接调用,性能好 | 反射调用,略有损耗 |
| 使用场景 | 简单场景,代理类少 | 复杂场景,需要通用增强 |
| 实际应用 | 装饰器模式 | Spring AOP、RPC 框架 |
七、面试答题要点
- 代理定义:为其他对象提供代理以控制访问,在客户端和目标对象之间起中介作用
- 代理作用:功能增强、控制访问、远程代理
- 静态代理问题:代码冗余、维护困难、扩展性差
- 动态代理优势:减少代码、提高维护性、灵活组合功能、支持 AOP
- 实际应用:Spring AOP、MyBatis Mapper、RPC 框架、日志监控
- 实现方式:JDK 动态代理(基于接口)、CGLIB(基于继承)
- 选择建议:简单场景用静态代理,复杂场景用动态代理