Java面试题 - 什么是代理模式?一般用在什么场景?

回答重点

代理模式(ProxyPattern)是一种结构型设计模式,在不改变原始对象的前提下,通过引入一个代理对象来控制对原始对象的访问,实现额外的功能。例如控制权限、延迟加载、缓存等。

这个模式在我们业务中太常见了,例如动态代理就是代理模式,SpringAOP就是动态代理,RPC框架也是使用了动态代理才使得调用远程方法和本地方法一样。

所以,统一报错、监控、限流、鉴权等等,需要跟业务解耦的功能,我们基本上都是使用代理类进行统一处理的。

代理模式的特点:

  • 间接访问:客户端通过代理访问实际对象,代理对象负责对实际对象的控制。
  • 功能增强:代理对象可以在访问实际对象之前或之后添加额外的功能。
  • 解耦性:客户端不直接与实际对象交互,通过代理对象可以透明地扩展实际对象的功能。

一般用在什么场景?

  • 访问控制:对实际对象的访问需要进行权限验证时。
  • 性能优化:通过代理实现缓存或延迟加载以提高系统性能。
  • 远程访问(rpc调用):客户端需要访问远程对象时,通过代理封装远程调用的细节。
  • 日志记录:在方法调用时添加日志记录功能。

典型场景:

  • 远程代理:为远程对象提供本地代表。
  • 权限代理:控制对对象的访问权限。
  • 智能引用:在访问对象时增加一些附加行为,如引用计数、日志记录等。

什么是代理模式?

代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在不改变原始类(或目标类)代码的情况下,通过引入代理类来给原始类提供附加功能。

Client
«interface»
Subject
+request()
RealSubject
+request()
Proxy
-realSubject: RealSubject
+request()

上面的类图展示了代理模式的基本结构:

  • Subject:定义了RealSubject和Proxy的公共接口
  • RealSubject:真实的对象,执行实际的业务逻辑
  • Proxy:代理对象,持有对RealSubject的引用,可以控制对RealSubject的访问

代理模式的应用场景

代理模式在软件开发中有广泛的应用,以下是几种常见的应用场景:

1. 远程代理(Remote Proxy)

为位于不同地址空间的对象提供一个本地代表。例如RPC框架中的存根(Stub)就是远程代理。

调用
网络请求
返回结果
返回结果
Client
Proxy
RemoteService

2. 虚拟代理(Virtual Proxy)

用于延迟加载大对象。例如在图片加载时,先显示缩略图或占位图,等真正需要时再加载大图。

// 虚拟代理示例代码
public class ImageProxy implements Image {
    private HighResolutionImage realImage;
    
    public void showImage() {
        if (realImage == null) {
            realImage = new HighResolutionImage(); // 真正需要时才加载
        }
        realImage.showImage();
    }
}

3. 保护代理(Protection Proxy)

控制对原始对象的访问权限。例如根据不同用户权限决定是否可以访问某些资源。

请求访问
检查权限
有权限
允许访问
无权限
拒绝访问
Client
Proxy
AuthService
RealSubject

4. 智能引用代理(Smart Reference Proxy)

在访问对象时执行附加操作,如:

  • 引用计数
  • 对象是否被锁定检查
  • 首次引用时持久化对象

5. 缓存代理(Cache Proxy)

为开销大的运算结果提供临时存储,下次请求相同数据时直接返回缓存结果。

代理模式的实现方式

代理模式主要有两种实现方式:

1. 静态代理

在编译时就已经确定代理关系,代理类和被代理类在编译时就确定了。

// 静态代理示例
public interface Subject {
    void request();
}

public class RealSubject implements Subject {
    public void request() {
        System.out.println("RealSubject处理请求");
    }
}

public class Proxy implements Subject {
    private RealSubject realSubject;
    
    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("代理后续处理");
    }
}

2. 动态代理

在运行时动态创建代理类,Java中常用的有JDK动态代理和CGLIB动态代理。

// JDK动态代理示例
public class DynamicProxyHandler implements InvocationHandler {
    private Object target;
    
    public DynamicProxyHandler(Object target) {
        this.target = target;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前操作");
        Object result = method.invoke(target, args);
        System.out.println("代理后操作");
        return result;
    }
    
    public static Object createProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new DynamicProxyHandler(target)
        );
    }
}

代理模式的优缺点

优点:

  1. 职责清晰:真实角色只需关注业务逻辑,其他事务由代理完成
  2. 高扩展性:可以在不修改目标对象的前提下扩展功能
  3. 智能化:动态代理可以实现更灵活的代理逻辑

缺点:

  1. 增加系统复杂度:引入了额外的代理层
  2. 性能开销:代理调用会带来一定的性能损耗,特别是远程代理
  3. 实现复杂度:动态代理的实现相对复杂

实际应用案例

Spring AOP中的代理模式

Spring AOP使用JDK动态代理(针对接口)或CGLIB(针对类)来实现面向切面编程。

调用方法
执行前置通知
调用目标方法
执行后置通知
返回结果
返回结果
返回结果
Client
Proxy
Aspect
Target

MyBatis中的代理模式

MyBatis的Mapper接口通过动态代理实现,开发者只需定义接口,MyBatis会在运行时生成实现类。

// MyBatis Mapper接口示例
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(int id);
}

// 实际使用时
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1); // 动态代理实现

总结

代理模式是一种非常有用的设计模式,它通过引入代理对象来控制对原始对象的访问。代理模式的主要应用场景包括远程调用、延迟加载、访问控制、智能引用和缓存等。根据实现方式的不同,代理模式可以分为静态代理和动态代理两种形式。

在实际开发中,代理模式被广泛应用于各种框架和库中,如Spring的AOP、MyBatis的Mapper接口、RPC框架等。合理使用代理模式可以有效地解耦业务逻辑与非功能性需求,提高代码的可维护性和扩展性。

理解代理模式的关键在于认识到它"控制访问"的本质,而不是"增强功能"(这是装饰器模式的特点)。当需要在访问对象时添加一些控制逻辑,而又不希望这些逻辑侵入原始对象时,代理模式是一个很好的选择。

更多推荐