【设计模式手册021】代理模式 - 如何控制对象访问

【设计模式手册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 代理模式的优点

  1. 职责清晰:将辅助功能从业务逻辑中分离
  2. 高扩展性:可以在不修改目标对象的基础上增强功能
  3. 智能化:代理可以执行额外的逻辑,如延迟加载、权限控制等
  4. 保护目标对象:代理可以控制对目标对象的访问

8.2 代理模式的缺点

  1. 复杂度增加:引入代理层会增加系统复杂度
  2. 性能开销:代理调用比直接调用稍慢
  3. 维护成本:需要维护额外的代理类

8.3 深入思考

代理模式的本质是**"访问控制的中介"**。它在不改变原始对象的情况下,通过引入一个中间层来控制对原始对象的访问,实现了关注点分离。

"代理模式就像现实世界中的秘书或代理人,他们帮你处理琐碎事务(日志、权限、缓存),让你(真实对象)可以专注于核心业务。这种分工协作的模式,让系统更加清晰和健壮。"

从源码的角度看,代理模式在Java中有着广泛应用:

  • Java动态代理(java.lang.reflect.Proxy
  • Spring AOP
  • MyBatis的Mapper接口代理
  • RPC框架的远程调用代理
  • Hibernate的延迟加载

何时使用代理模式

  • 需要控制对对象的访问时
  • 需要在访问对象时添加额外功能时
  • 需要延迟创建昂贵对象时
  • 需要本地代表远程对象时(远程代理)
  • 需要缓存昂贵操作的结果时(缓存代理)

使用场景

  • 权限控制和安全代理
  • 日志记录和监控代理
  • 延迟加载和虚拟代理
  • 缓存代理
  • 远程方法调用(RMI)代理

下一篇预告:设计模式手册022 - 抽象工厂模式


版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

相关推荐
山沐与山2 小时前
【设计模式】Python策略模式:从入门到实战
python·设计模式·策略模式
阿拉斯攀登3 小时前
设计模式:责任链模式(mybatis数据权限实现)
设计模式·mybatis·责任链模式
syt_10133 小时前
设计模式之-模板模式
设计模式
阿拉斯攀登3 小时前
设计模式:责任链模式(MyBatis)
设计模式·mybatis·责任链模式
崎岖Qiu4 小时前
【设计模式笔记19】:建造者模式
java·笔记·设计模式·建造者模式
syt_10134 小时前
设计模式之-享元模式
javascript·设计模式·享元模式
想学后端的前端工程师18 小时前
【Java设计模式实战应用指南:23种设计模式详解】
java·开发语言·设计模式
Revol_C19 小时前
开箱即用!轻量级轮询方案,支持同步获取轮询结果!
前端·javascript·设计模式
聪明努力的积极向上1 天前
【设计】分批查询数据通用方法(基于接口 + 泛型 + 定点复制)
开发语言·设计模式·c#