结构型模式

代理模式(Proxy Pattern)为其他对象提供一种代理,以控制对这个对象的访问。

组成

代理模式的实现需要3个参与者:接口—Subject、接口的具体实现类—RealSubject、对象的代理—Proxy。

  • Subject:定义的接口
  • RealSubject:接口的具体实现类,需要被代理的真实类
  • Proxy:代理类,提供与 Subject 相同的方法,并持有对 RealSubject 的一个引用,这样可以代替 RealSubject,对外提供相同的方法

代理模式的组成UML如图1所示(来自参考文献[1]):
image.png
图1. 代理模式的组成
协作

  • 客户创建 Proxy 对象,同时在 Proxy 对象中创了 RealSubject 对象
  • 客户端调用 Proxy 的方法,调用 RealSubject 的同名方法,转发请求到 RealSubject

应用场景

在需要用比较通用和复杂的对象指针代替简单的指针的时候,需要使用 Proxy 模式(使用简单对象实现复杂功能),下面是一些可以使用 Proxy 模式的常见情况:
1、远程代理(Remote Proxy):为一个在不同地址空间的对象提供代理
2、需代理(Virtual Proxy):根据需要创建开销很大的对象,延迟对开销很大的对象的访问,实现懒加载
3、保护代理(Protection Proxy):控制对原始对象的访问,用于对象有不同访问权限的时候
4、智能指引(Smart Reference):它在访问对象时执行一些附加操作,比如对实际对象的引用计数、访问一个对象前检查是否已经锁定等

示例代码

最常见的代理模式应用,是JDK中的静态代理和动态代理。

静态代理

静态代理完全按照图1所示的结构,分别创建 Subject、RealSubject、Proxy 类,使用Proxy 实现 Subject 并持有 RealSubject,示例如下。
1、创建 Subject 接口和 RealSubject 类

  1. // 被代理的接口
  2. public interface UserService {
  3. /**
  4. * 根据ID查询用户对象
  5. * @param id 用户ID
  6. * @return User
  7. */
  8. User getUserById(Long id);
  9. }
  10. // 被代理的实现类
  11. @Slf4j
  12. public class UserServiceImpl implements UserService {
  13. @Override
  14. public User getUserById(Long id) {
  15. User user = new User().setId(id);
  16. log.info("user ===== {}", user);
  17. return user;
  18. }
  19. }

2、创建 Proxy 类

  1. @Slf4j
  2. public class UserProxy implements UserService {
  3. private UserService userService;
  4. public UserProxy(UserService userService) {
  5. this.userService = userService;
  6. }
  7. @Override
  8. public User getUserById(Long id) {
  9. log.info("before getUserById === class: {}", this.getClass().getName());
  10. User user = userService.getUserById(id);
  11. log.info("after getUserById === class: {}", this.getClass().getName());
  12. return user;
  13. }
  14. }

3、创建代理对象,并调用 ReaslSubject 中的方法

  1. public static void main(String[] args) {
  2. UserService proxy = new UserProxy(new UserServiceImpl());
  3. proxy.getUserById(1000L);
  4. }
  5. // ========== 打印结果 =============== //
  6. ===== UserProxy : getUserById : begin
  7. ===== UserServiceImpl : getUserById : id:1000 =====
  8. ===== UserProxy : getUserById : end

proxy 在执行同名方法时,会先执行自身的逻辑,然后再执行 RealSubject 的逻辑,从而实现了对真实对象的方法控制。
静态代理的缺点很明显:要求代理类实现与被代理类相同的接口,一个代理类只能服务于一个类,如果要服务于多个类需要创建多个代理类。因此,JDK中更常用的是动态代理。

JDK 动态代理

JDK提供了动态代理,与静态代理相比最大的不同在于:Proxy 代理类不再是固定的,而是在运行时动态创建的。
JDK怎么动态地创建Proxy?
Java对象是由JVM根据编译完成的 .class 文件创建的,因此在动态代理中,要想动态创建 Proxy 对象,并且持有 RealSubject 的引用,需要完成以下几个步骤:
1、创建 Proxy 的 .class 文件,并加载到 JVM 中,创建 Class 对象
2、使用 Proxy 的 Class 对象,调用构造器创建对象实例
3、Proxy 对象直接或间接持有 RealSubject 对象的引用,在调用同名方法时能够真正调用 RealSubject 的方法

JDK动态代理的使用示例如下。
1、创建 Subject 接口和 RealSubject 类,这部分与静态代理相同

  1. // 被代理的接口
  2. public interface UserService {
  3. /**
  4. * 根据ID查询用户对象
  5. * @param id 用户ID
  6. * @return User
  7. */
  8. User getUserById(Long id);
  9. }
  10. // 被代理的实现类
  11. @Slf4j
  12. public class UserServiceImpl implements UserService {
  13. @Override
  14. public User getUserById(Long id) {
  15. User user = new User().setId(id);
  16. log.info("user ===== {}", user);
  17. return user;
  18. }
  19. }

2、创建 InvocationHandler 接口的实现类

  1. @Slf4j
  2. public class UserInvokeHandler implements InvocationHandler {
  3. // 被代理的 RealSubject 对象
  4. private Object target;
  5. public UserInvokeHandler(Object target) {
  6. this.target = target;
  7. }
  8. @Override
  9. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  10. log.info("=== UserInvokeHandler invoke begin ===");
  11. Object result = method.invoke(target, args);
  12. log.info("=== UserInvokeHandler invoke end ===");
  13. return result;
  14. }
  15. }

3、使用 Proxy.newProxyInstance() 创建代理类对象

  1. public static void main(String[] args) {
  2. UserInvokeHandler handler = new UserInvokeHandler(new UserServiceImpl());
  3. UserService proxy = (UserService) Proxy.newProxyInstance(
  4. handler.getClass().getClassLoader(), new Class[]{UserService.class}, handler);
  5. proxy.getUserById(200L);
  6. }
  7. // ========== 打印结果 =============== //
  8. === UserInvokeHandler invoke begin ===
  9. ===== UserServiceImpl : getUserById : id:200 =====
  10. === UserInvokeHandler invoke end ===

在动态代理的示例代理中,主要有3个步骤:
1、创建需要被代理的接口和实现类,这一步与静态代理相同
2、创建 InvocationHandler 接口的实现类, invoke 方法里实现控制逻辑,把 RealSubject 对象作为参数传入,从而让代理类间接持有 RealSubject 的引用。
3、使用 Proxy.newProxyInstance() 方法创建代理类对象
在调用接口方法时,实际上是调用 handler 的 invoke() 方法,相关的源码介绍。
JDK动态代理被广泛使用在各个框架中,比如 MyBatis 框架,下面简单介绍一下 MyBatis 中对JDK动态代理的使用。

MyBatis

在MyBatis测试用例中,使用 sqlSession.getMappser(Mapper.class) 创建 Mapper 接口的代理对象,再调用同名方法,使用的就是JDK动态代理实现的。

  1. @Test // see issue #448
  2. void shouldGetAUserStatic() {
  3. try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
  4. Mapper mapper = sqlSession.getMapper(Mapper.class);
  5. User user = mapper.getUserStatic(1);
  6. Assertions.assertNotNull(user);
  7. Assertions.assertEquals("User1", user.getName());
  8. }
  9. }

sqlSession.getMapper()的时序图如图2所示:
image.png
图2. sqlSession.getMapper() 的时序图
MapperProxyFactory.newInstance 中使用 Proxy.newProxyInstance() 创建 Mpper 的代理对象,传入的参数 MapperProxy 就是 InvocationHandler 接口的实现类:

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {
  2. // ..... 省略 ........ //
  3. @Override
  4. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  5. try {
  6. if (Object.class.equals(method.getDeclaringClass())) {
  7. return method.invoke(this, args);
  8. } else {
  9. return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
  10. }
  11. } catch (Throwable t) {
  12. throw ExceptionUtil.unwrapThrowable(t);
  13. }
  14. }
  15. }

优点和缺点

优点:
1、高扩展性:不需要对真实类 RealSubject 进行修改,就可以扩展它的功能
2、把控制和职责分离,与 RealSubject 无关的逻辑可以由代理类实现
缺点:
1、额外增加了代理类,特别是动态代理在运行时创建 .class 字节码和 对象,造成性能下降**

总结

1、使用代理模式控制对真实类的访问、提供与真实类无关的逻辑实现等
2、使用动态代理在运行时创建接口的代理实现

附录. 参考文献

  1. Erich Gamma, Richard Helm, Ralph Johnson, Jojn Vlissides. 设计模式—可复用面向对象软件的基础[M]. 机械工业出版社,北京. 2018.11
  2. 设计模式最佳套路3 —— 愉快地使用代理模式