关于设计模式:设计模式中的俄罗斯套娃装饰者Decorator模式

俄罗斯套娃想必大家都不生疏,就是同一种玩具娃娃大的套小的,而后一层一层嵌套上来。

在设计模式中,有一种罕用的套娃模式,叫做装璜者(Decorator)模式,又称为包装(Wrapper)模式。

HttpServletRequest 套娃

在 Spring 框架开发的 Web 利用中,如果应用了 Spring Security 或 Spring Session,用 Debug 模式察看一下某个申请对应的 HttpServletRequest 对象,会发现这就是一个俄罗斯套娃:

图中能够看到咱们拿到的 HttpServletRequest 对象,外部成员中蕴含了一个 HttpServletRequest 对象,而这个外部的 HttpServletRequest 对象外部又蕴含了一个 HttpServletRequest 对象,层层蕴含,层层套娃。这就是一个典型的装璜者模式。

咱们晓得,HttpServletRequest 是 Servlet 标准中提供的一个 interface 接口。Servlet 标准自身没有实现 HttpServletRequest 接口,HttpServletRequest 接口个别是由 Servlet 容器来实现,例如 Tomcat、Jetty。如果 Spring Security、Spring Session 等框架想要加强 HttpServletRequest 对象的性能,然而不扭转原有对象的接口,最好的方法就是应用装璜者模式。例如:

  • Spring Security 加强了 HttpServletRequest.getRemoteUser() 办法,可返回以后通过 Spring Security 框架登录用户的用户名;
  • Spring Session 加强了 HttpServletRequest.getSession() 办法,加强后的 Session 取代了 Servlet 容器的默认实现,其读写能够应用一个集中式的存储,例如 Redis,这样能够不便集群中的多个实例共享 Session。

HttpServletRequestWrapper / ServletRequestWrapper

javax.servlet.http 包下有个 HttpServletRequestWrapper 类[源码],继承自 ServletRequestWrapper 类[源码]。能够看到这两个类上的正文:

This class implements the Wrapper or Decorator pattern. Methods default to calling through to the wrapped request object.

翻译:这个类实现了装璜者模式/包装模式,办法模式会间接调用外部包装的 request 对象。

ServletRequestWrapper 自身实现了 ServletRequest 接口,它的构造方法要求传入另一个 ServletRequest 对象,并将这个对象赋值给外部 request 对象:

public class ServletRequestWrapper implements ServletRequest {

    private ServletRequest request;

    public ServletRequestWrapper(ServletRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");   
        }
        this.request = request;
    }
    
    // ...
}

ServletRequestWrapperServletRequest 接口办法的实现,则是间接调用外部 request 对象对应的办法:

public String getContentType() {
    return this.request.getContentType();
}

public ServletInputStream getInputStream() throws IOException {
    return this.request.getInputStream();
}

public String getParameter(String name) {
    return this.request.getParameter(name);
}

// ...

以上就是一个最根本的装璜器。咱们能够间接拿来套娃:

HttpServletRequest request = ...; // 已有的 request 对象
HttpServletRequest requestWrapper = new HttpServletRequestWrapper(request); // 包装后的对象

当然,下面代码没有任何意义,因为 requestWrapper 没有做任何扩大,应用 requestWrapper 对象和间接用 request 对象没有任何区别。真正的装璜者类会继承 ServletRequestWrapper 并在此基础上做加强。

上面,咱们再看下 Spring Security 和 Spring Session 如何对 HttpServletRequest 对象进行装璜。

Spring Security / Spring Session 中的装璜者实现

在 Spring Security 文档 Servlet API integration 中,能够看到 Spring Security 框架对 HttpServletRequest 对象的 getRemoteUser()getUserPrincipal()isUserInRole(String) 等办法进行了加强,例如 getRemoteUser() 办法能够间接返回以后登录用户的用户名。接下来看一下 Spring Security 如何加强这些办法。

首先,Spring Security 提供了一个过滤器 SecurityContextHolderAwareRequestFilter,对相干申请进行过滤解决。在 SecurityContextHolderAwareRequestFilter 第 149 行 联合 HttpServlet3RequestFactory 第 163 行 能够看到,这个 Filter 中创立了一个新的 Servlet3SecurityContextHolderAwareRequestWrapper 对象,这个类继承自 HttpServletRequestWrapper 类,并加强了相干办法。其父类 SecurityContextHolderAwareRequestWrapper 类[源码]中能够看到对 getRemoteUser() 办法的加强:

public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper {

    @Override
    public String getRemoteUser() {
        Authentication auth = getAuthentication();
        if ((auth == null) || (auth.getPrincipal() == null)) {
            return null;
        }
        if (auth.getPrincipal() instanceof UserDetails) {
            return ((UserDetails) auth.getPrincipal()).getUsername();
        }
        if (auth instanceof AbstractAuthenticationToken) {
            return auth.getName();
        }
        return auth.getPrincipal().toString();
    }
    
    // ...
}

简略来讲,就是 Spring Security 通过一个 Filter 过滤相干申请,拿到原始的 HttpServletRequest 对象,通过一个继承自 HttpServletRequestWrapper 类的装璜者,加强了 getRemoteUser() 等相干办法,再将加强后的对象传给后续的业务解决,那么后续咱们在 Controller 层拿到的 HttpServletRequest 对象就能够间接应用 getRemoteUser() 等办法。

Spring Session 实现和 Spring Security 相似,这里就不再反复介绍,有趣味能够看 SessionRepositoryFilter 源码。

Collections 中的装璜者

装璜者模式岂但能够加强被装璜者的性能,还能够禁用某些性能。当然,禁用实际上也是一种“加强”。

例如,假如有一个 List,当咱们须要将这个 List 传给第三方的某个办法去读,然而因为这个第三方办法不可信,为了避免这个办法对 List 篡改,能够通过装璜器模式禁用 List 的批改办法,装璜成一个只读的 List。

java.util.Collections 中提供了一个静态方法 unmodifiableList(List),用于将一个 List 封装为只读的 List:

List<String> list = ...;
List<String> unmodifiableList = Collections.unmodifiableList(list);

通过这个办法的源码能够看到,Collections.unmodifiableList(List) 办法实际上返回了一个 UnmodifiableListUnmodifiableList 是一个典型的装璜者,其外部对 List 的读相干办法间接调用被装璜对象的对应办法,而对写相干办法做了限度,抛出 UnsupportedOperationException。上面是 UnmodifiableList 的局部源码:

static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                                  implements List<E> {
    final List<? extends E> list;

    UnmodifiableList(List<? extends E> list) {
        super(list);
        this.list = list;
    }

    public E get(int index) {
        return list.get(index);
    }
    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
    public int indexOf(Object o) {
        return list.indexOf(o);
    }
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new UnsupportedOperationException();
    }
    
    // ...
}

java.util.Collections 中还提供了其余一系列装璜者:

  • unmodifiableSet(Set)unmodifiableMap(Map) 等办法和 unmodifiableList(List) 相似,用于不同类型的汇合的装璜
  • synchronizedList(List)synchronizedSet(Set)synchronizedMap(Map) 等办法应用 synchronized 装璜 List、Set、Map 中的相干办法,返回一个线程平安的汇合
  • checkedList(List, Class)checkedSet(Set, Class)checkedMap(List, Class, Class) 等办法返回类型平安的汇合,如果插入汇合的元素类型不符合要求则会抛出异样

InputStream 装璜者

装璜者岂但能够加强被装璜者原有的办法,还能够减少新的办法扩大性能。

java.io 包中,针对 InputStream 有一个根底的形象装璜者 FilterInputStream,其源码如下:

public class FilterInputStream extends InputStream {

    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }
    
    // ...
}

相似于下面讲到的 HttpServletRequestWrapper 类,FilterInputStream 是一个根底的装璜者,它的子类才是具体的装璜者的实现。DataInputStream 就是其中一个典型的装璜者实现。

DataInputStream 用于从被装璜的 InputStream 对象中读取根本数据类型,它继承自 FilterInputStream,并新增了新的办法,如 readByte()readInt()readFloat() 等,这些办法是 InputStream 接口中没有的。

除了 DataInputStream 之外,FilterInputStream 常见的子类装璜者还有:

  • BufferedInputStream 为被装璜的 InputStream 提供缓冲性能以及反对 markreset 办法
  • CipherInputStream 应用加密算法(例如 AES)对 InputStream 中的数据加密或解密
  • DeflaterInputStreamInflaterInputStream 应用 deflate 压缩算法对 InputStream 中的数据压缩或解压

装璜者模式构造

图片起源: https://refactoringguru.cn/de…

上面总结一下在后面的例子中,各个类和上图中的对应关系:

  • 部件(Component)对应有 HttpServletRequestListInputStream
  • 根底装璜(Base Decorator)对应有 HttpServletRequestWrapperFilterInputStream
  • 具体装璜类(Concrete Decorators)对应有 Servlet3SecurityContextHolderAwareRequestWrapperUnmodifiableListDataInputStream

关注我的公众号

微信搜一搜 Java论道 关注我

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据