【设计模式手册021】代理模式 - 如何控制对象访问
1. 我们为何需要代理模式?
在软件设计中,我们经常会遇到这样的场景:我们想要增强或控制对某个对象的访问,但又不想修改原始对象。比如:
- 权限控制:只有特定用户才能执行某些操作
- 延迟加载:只有在真正需要时才创建昂贵对象
- 日志记录:记录方法的调用信息和参数
- 性能监控:监控方法的执行时间
- 缓存:缓存方法调用的结果
- 远程调用:本地调用远程对象
初级程序员的写法:
java
public class UserService {
public void addUser(String user) {
// 记录日志
System.out.println("开始添加用户: " + user);
long start = System.currentTimeMillis();
// 权限检查
if (!checkPermission()) {
System.out.println("权限不足");
return;
}
// 核心业务逻辑
// ... 添加用户的逻辑
long end = System.currentTimeMillis();
System.out.println("添加用户完成,耗时: " + (end - start) + "ms");
}
public void deleteUser(String user) {
// 同样的,每个方法都要重复这些逻辑
System.out.println("开始删除用户: " + user);
long start = System.currentTimeMillis();
if (!checkPermission()) {
System.out.println("权限不足");
return;
}
// ... 删除用户的逻辑
long end = System.currentTimeMillis();
System.out.println("删除用户完成,耗时: " + (end - start) + "ms");
}
private boolean checkPermission() {
// 检查权限
return true;
}
}
这种写法的痛点:
- ❌ 代码重复:每个方法都要重复日志、权限检查等逻辑
- ❌ 职责混乱:业务逻辑与非业务逻辑混杂在一起
- ❌ 难以维护:如果要修改权限检查逻辑,需要修改所有方法
- ❌ 违反开闭原则:对扩展开放,对修改关闭
2. 代理模式:本质与定义
2.1 模式定义
代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。
2.2 模式结构
java
// 抽象主题
public interface Subject {
void request();
}
// 真实主题
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实主题的请求");
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
private void preRequest() {
System.out.println("代理前预处理");
}
private void postRequest() {
System.out.println("代理后处理");
}
}
3. 深入理解
3.1 静态代理
核心思想:代理类和真实类实现同一接口,代理类内部持有真实类的引用。
java
// 用户服务接口
public interface UserService {
void addUser(String user);
void deleteUser(String user);
}
// 真实用户服务
public class UserServiceImpl implements UserService {
@Override
public void addUser(String user) {
System.out.println("添加用户: " + user);
}
@Override
public void deleteUser(String user) {
System.out.println("删除用户: " + user);
}
}
// 静态代理
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(String user) {
long start = System.currentTimeMillis();
System.out.println("开始添加用户");
userService.addUser(user);
long end = System.currentTimeMillis();
System.out.println("添加用户完成,耗时: " + (end - start) + "ms");
}
@Override
public void deleteUser(String user) {
long start = System.currentTimeMillis();
System.out.println("开始删除用户");
userService.deleteUser(user);
long end = System.currentTimeMillis();
System.out.println("删除用户完成,耗时: " + (end - start) + "ms");
}
}
3.2 动态代理
设计原则的体现:使用Java反射机制在运行时动态创建代理类。
java
// 动态代理处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("开始执行: " + method.getName());
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("执行完成: " + method.getName() + ",耗时: " + (end - start) + "ms");
return result;
}
}
// 使用动态代理
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new DynamicProxyHandler(userService)
);
proxy.addUser("张三");
3.3 CGLIB代理
对于没有实现接口的类,可以使用CGLIB进行代理。
java
// CGLIB方法拦截器
public class CglibProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("开始执行: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
long end = System.currentTimeMillis();
System.out.println("执行完成: " + method.getName() + ",耗时: " + (end - start) + "ms");
return result;
}
}
// 使用CGLIB代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new CglibProxyInterceptor());
UserService proxy = (UserService) enhancer.create();
4. 实战案例:完整的权限控制和日志记录系统
让我们来看一个完整的例子:
java
// 用户实体
@Data
@AllArgsConstructor
public class User {
private String username;
private String role;
}
// 业务服务接口
public interface BusinessService {
void sensitiveOperation(User user);
void normalOperation(User user);
}
// 真实业务服务
@Slf4j
public class BusinessServiceImpl implements BusinessService {
@Override
public void sensitiveOperation(User user) {
log.info("执行敏感操作,用户: {}", user.getUsername());
// 敏感业务逻辑
}
@Override
public void normalOperation(User user) {
log.info("执行普通操作,用户: {}", user.getUsername());
// 普通业务逻辑
}
}
// 权限检查器
@Slf4j
public class PermissionChecker {
public static boolean checkPermission(User user, String operation) {
if ("admin".equals(user.getRole())) {
return true;
}
if ("sensitiveOperation".equals(operation) && !"admin".equals(user.getRole())) {
log.warn("用户 {} 没有执行 {} 的权限", user.getUsername(), operation);
return false;
}
return true;
}
}
// 动态代理处理器
@Slf4j
public class SecurityLoggingHandler implements InvocationHandler {
private Object target;
public SecurityLoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法参数中的User对象(这里假设第一个参数是User)
User user = null;
if (args != null && args.length > 0 && args[0] instanceof User) {
user = (User) args[0];
}
// 权限检查
if (user != null && !PermissionChecker.checkPermission(user, method.getName())) {
throw new SecurityException("权限不足");
}
// 记录方法开始
long startTime = System.currentTimeMillis();
log.info("方法开始: {},用户: {},参数: {}",
method.getName(), user != null ? user.getUsername() : "未知",
Arrays.toString(args));
try {
// 执行实际方法
Object result = method.invoke(target, args);
// 记录方法成功结束
long endTime = System.currentTimeMillis();
log.info("方法成功: {},执行时间: {}ms",
method.getName(), endTime - startTime);
return result;
} catch (Exception e) {
// 记录方法异常
long endTime = System.currentTimeMillis();
log.error("方法异常: {},执行时间: {}ms,异常: {}",
method.getName(), endTime - startTime, e.getMessage());
throw e;
}
}
}
// 代理工厂
public class ProxyFactory {
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class<?>[]{interfaceType},
new SecurityLoggingHandler(target)
);
}
}
// 使用示例
@Slf4j
public class ProxyDemo {
public static void main(String[] args) {
// 创建真实对象
BusinessService realService = new BusinessServiceImpl();
// 创建代理对象
BusinessService proxyService = ProxyFactory.createProxy(realService, BusinessService.class);
// 创建测试用户
User admin = new User("admin", "admin");
User normalUser = new User("user1", "user");
// 测试权限控制
try {
proxyService.sensitiveOperation(admin); // 应该成功
proxyService.sensitiveOperation(normalUser); // 应该失败
} catch (SecurityException e) {
log.error("安全异常: {}", e.getMessage());
}
// 测试普通操作
proxyService.normalOperation(admin);
proxyService.normalOperation(normalUser);
}
}
5. Spring Boot中的实现
在Spring Boot中,我们可以利用AOP让代理模式更加优雅:
java
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String value() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {
String[] roles() default {"admin"};
}
// 切面配置
@Aspect
@Component
@Slf4j
public class SecurityLoggingAspect {
// 定义切点:带有LogExecution注解的方法
@Pointcut("@annotation(com.example.demo.annotation.LogExecution)")
public void logExecutionPointcut() {}
// 定义切点:带有RequiredPermission注解的方法
@Pointcut("@annotation(com.example.demo.annotation.RequiredPermission)")
public void requiredPermissionPointcut() {}
// 环绕通知:日志记录
@Around("logExecutionPointcut()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
log.info("开始执行: {}.{},参数: {}", className, methodName, Arrays.toString(joinPoint.getArgs()));
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("执行完成: {}.{},耗时: {}ms", className, methodName, endTime - startTime);
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
log.error("执行异常: {}.{},耗时: {}ms,异常: {}",
className, methodName, endTime - startTime, e.getMessage());
throw e;
}
}
// 前置通知:权限检查
@Before("requiredPermissionPointcut()")
public void checkPermission(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RequiredPermission annotation = method.getAnnotation(RequiredPermission.class);
// 获取方法参数中的User对象
Object[] args = joinPoint.getArgs();
User user = findUserInArgs(args);
if (user == null) {
throw new SecurityException("未找到用户信息");
}
// 检查权限
String[] requiredRoles = annotation.roles();
if (!hasRequiredRole(user, requiredRoles)) {
throw new SecurityException("用户 " + user.getUsername() + " 没有执行该操作的权限");
}
log.info("权限检查通过: {},用户: {}", method.getName(), user.getUsername());
}
private User findUserInArgs(Object[] args) {
for (Object arg : args) {
if (arg instanceof User) {
return (User) arg;
}
}
return null;
}
private boolean hasRequiredRole(User user, String[] requiredRoles) {
return Arrays.stream(requiredRoles)
.anyMatch(role -> role.equals(user.getRole()));
}
}
// 业务服务
@Service
@Slf4j
public class AdvancedBusinessService {
@LogExecution
@RequiredPermission(roles = {"admin"})
public void sensitiveOperation(User user) {
log.info("执行敏感操作,用户: {}", user.getUsername());
// 敏感业务逻辑
}
@LogExecution
@RequiredPermission(roles = {"admin", "user"})
public void normalOperation(User user) {
log.info("执行普通操作,用户: {}", user.getUsername());
// 普通业务逻辑
}
@LogExecution
public void publicOperation(User user) {
log.info("执行公开操作,用户: {}", user.getUsername());
// 公开业务逻辑,不需要特定权限
}
}
// 配置类
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// 自动配置AOP
}
// REST控制器
@RestController
@RequestMapping("/api/business")
@Slf4j
public class BusinessController {
private final AdvancedBusinessService businessService;
public BusinessController(AdvancedBusinessService businessService) {
this.businessService = businessService;
}
@PostMapping("/sensitive")
public ResponseEntity<String> sensitiveOperation(@RequestBody User user) {
try {
businessService.sensitiveOperation(user);
return ResponseEntity.ok("敏感操作执行成功");
} catch (SecurityException e) {
return ResponseEntity.status(403).body(e.getMessage());
}
}
@PostMapping("/normal")
public ResponseEntity<String> normalOperation(@RequestBody User user) {
try {
businessService.normalOperation(user);
return ResponseEntity.ok("普通操作执行成功");
} catch (SecurityException e) {
return ResponseEntity.status(403).body(e.getMessage());
}
}
@PostMapping("/public")
public ResponseEntity<String> publicOperation(@RequestBody User user) {
businessService.publicOperation(user);
return ResponseEntity.ok("公开操作执行成功");
}
}
6. 代理模式的变体与进阶用法
6.1 虚拟代理(延迟加载)
java
// 虚拟代理:延迟加载大对象
public class VirtualProxy implements ExpensiveObject {
private RealExpensiveObject realObject;
@Override
public void process() {
if (realObject == null) {
realObject = new RealExpensiveObject(); // 延迟创建
}
realObject.process();
}
}
// 使用虚拟代理
ExpensiveObject proxy = new VirtualProxy();
// 此时真实对象尚未创建
proxy.process(); // 第一次调用时创建真实对象
6.2 保护代理
java
// 保护代理:控制访问权限
public class ProtectionProxy implements SensitiveOperation {
private RealSensitiveOperation realOperation;
private User user;
public ProtectionProxy(User user) {
this.user = user;
this.realOperation = new RealSensitiveOperation();
}
@Override
public void operation() {
if (!"admin".equals(user.getRole())) {
throw new SecurityException("权限不足");
}
realOperation.operation();
}
}
6.3 缓存代理
java
// 缓存代理:缓存方法结果
public class CacheProxy implements DataService {
private RealDataService realService;
private Map<String, Object> cache = new HashMap<>();
public CacheProxy(RealDataService realService) {
this.realService = realService;
}
@Override
public Object getData(String key) {
if (cache.containsKey(key)) {
System.out.println("从缓存获取数据: " + key);
return cache.get(key);
}
Object data = realService.getData(key);
cache.put(key, data);
System.out.println("从真实服务获取数据并缓存: " + key);
return data;
}
}
7. 代理模式 vs 其他模式
7.1 代理模式 vs 装饰器模式
- 代理模式:控制访问,重点在于访问控制、延迟加载等
- 装饰器模式:增强功能,重点在于动态添加新功能
7.2 代理模式 vs 适配器模式
- 代理模式:不改变接口,控制对原有接口的访问
- 适配器模式:改变接口,使不兼容的接口能够一起工作
7.3 代理模式 vs 门面模式
- 代理模式:一对一的关系,代理一个对象
- 门面模式:一对多的关系,为多个对象提供统一接口
8. 总结与思考
8.1 代理模式的优点
- 职责清晰:将辅助功能从业务逻辑中分离
- 高扩展性:可以在不修改目标对象的基础上增强功能
- 智能化:代理可以执行额外的逻辑,如延迟加载、权限控制等
- 保护目标对象:代理可以控制对目标对象的访问
8.2 代理模式的缺点
- 复杂度增加:引入代理层会增加系统复杂度
- 性能开销:代理调用比直接调用稍慢
- 维护成本:需要维护额外的代理类
8.3 深入思考
代理模式的本质是**"访问控制的中介"**。它在不改变原始对象的情况下,通过引入一个中间层来控制对原始对象的访问,实现了关注点分离。
"代理模式就像现实世界中的秘书或代理人,他们帮你处理琐碎事务(日志、权限、缓存),让你(真实对象)可以专注于核心业务。这种分工协作的模式,让系统更加清晰和健壮。"
从源码的角度看,代理模式在Java中有着广泛应用:
- Java动态代理(
java.lang.reflect.Proxy) - Spring AOP
- MyBatis的Mapper接口代理
- RPC框架的远程调用代理
- Hibernate的延迟加载
何时使用代理模式:
- 需要控制对对象的访问时
- 需要在访问对象时添加额外功能时
- 需要延迟创建昂贵对象时
- 需要本地代表远程对象时(远程代理)
- 需要缓存昂贵操作的结果时(缓存代理)
使用场景:
- 权限控制和安全代理
- 日志记录和监控代理
- 延迟加载和虚拟代理
- 缓存代理
- 远程方法调用(RMI)代理
下一篇预告:设计模式手册022 - 抽象工厂模式
版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。