代理模式 (Proxy Pattern)
为其他对象提供一种代理以控制对这个对象的访问
目录
概述
代理模式是一种结构型设计模式,它为另一个对象提供一个替身或占位符,以控制对这个对象的访问。
模式结构
代理类型
- 静态代理:编译时确定
- 动态代理:运行时生成
- 虚拟代理:延迟创建开销大的对象
- 保护代理:控制访问权限
- 缓存代理:缓存结果
静态代理
// 主题接口
public interface Image {
void display();
}
// 真实主题
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("加载图片: " + fileName);
}
@Override
public void display() {
System.out.println("显示图片: " + fileName);
}
}
// 代理
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// 使用
Image image = new ProxyImage("photo.jpg");
// 第一次调用时才加载
image.display();动态代理
JDK 动态代理
// 接口
public interface UserService {
void addUser(String name);
void deleteUser(Long id);
}
// 实现
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
@Override
public void deleteUser(Long id) {
System.out.println("删除用户: " + id);
}
}
// 代理处理器
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[LOG] 调用方法: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("[LOG] 方法结束: " + method.getName());
return result;
}
}
// 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.addUser("张三");CGLIB 动态代理
// CGLIB 代理(无需接口)
public class CglibProxy implements MethodInterceptor {
public Object createProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB] 前置处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("[CGLIB] 后置处理");
return result;
}
}
// 使用
CglibProxy cglibProxy = new CglibProxy();
UserService proxy = (UserService) cglibProxy.createProxy(UserServiceImpl.class);
proxy.addUser("李四");框架中的应用
Spring AOP
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 事务管理由代理实现
}
@Cacheable("orders")
public Order getOrder(Long id) {
// 缓存由代理实现
}
}
// Spring 会自动创建代理
// 1. 如果有接口,使用 JDK 动态代理
// 2. 如果没有接口,使用 CGLIB 代理JPA 延迟加载
@Entity
public class Department {
@OneToMany(fetch = FetchType.LAZY)
private List<Employee> employees; // 代理对象
}
// employees 在访问时才真正加载
Department dept = departmentRepository.findById(1L);
// 此时 employees 是代理对象
for (Employee e : dept.getEmployees()) {
// 第一次遍历时才真正查询数据库
System.out.println(e.getName());
}MyBatis Mapper
// MyBatis 使用代理实现 Mapper 接口
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);
}
// 实际使用时,MyBatis 返回的是代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// mapper 是代理对象,实际执行时调用 SqlSession实际应用:权限控制代理
public interface DocumentService {
void readDocument(String docId);
void writeDocument(String docId, String content);
void deleteDocument(String docId);
}
public class SecurityProxy implements DocumentService {
private DocumentService target;
private User currentUser;
public SecurityProxy(DocumentService target, User user) {
this.target = target;
this.currentUser = user;
}
@Override
public void readDocument(String docId) {
if (hasPermission("READ")) {
target.readDocument(docId);
} else {
throw new SecurityException("无读取权限");
}
}
@Override
public void writeDocument(String docId, String content) {
if (hasPermission("WRITE")) {
target.writeDocument(docId, content);
} else {
throw new SecurityException("无写入权限");
}
}
private boolean hasPermission(String permission) {
return currentUser.getPermissions().contains(permission);
}
}缓存代理
public class CacheProxy implements UserService {
private UserService target;
private Map<Long, User> cache = new ConcurrentHashMap<>();
@Override
public User getUser(Long id) {
User user = cache.get(id);
if (user == null) {
user = target.getUser(id);
cache.put(id, user);
}
return user;
}
@Override
public void updateUser(User user) {
target.updateUser(user);
cache.put(user.getId(), user);
}
}代理 vs 装饰器
| 代理 | 装饰器 |
|---|---|
| 控制访问 | 添加功能 |
| 目标对象通常隐藏 | 目标对象可灵活组合 |
| 侧重访问控制 | 侧重功能扩展 |
优缺点
| 优点 | 缺点 |
|---|---|
| 职责分离 | 增加复杂度 |
| 保护目标对象 | 可能降低性能 |
| 扩展目标功能 |
总结
代理模式是 Spring AOP、Hibernate 延迟加载等框架的基础。理解 JDK 动态代理和 CGLIB 代理的区别对使用 Spring 框架很重要。