俄罗斯套娃想必大家都不生疏,就是同一种玩具娃娃大的套小的,而后一层一层嵌套上来。
在设计模式中,有一种罕用的套娃模式,叫做装璜者(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;
}
// ...
}
ServletRequestWrapper
对 ServletRequest
接口办法的实现,则是间接调用外部 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)
办法实际上返回了一个 UnmodifiableList
。UnmodifiableList
是一个典型的装璜者,其外部对 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
提供缓冲性能以及反对mark
和reset
办法CipherInputStream
应用加密算法(例如 AES)对InputStream
中的数据加密或解密DeflaterInputStream
、InflaterInputStream
应用 deflate 压缩算法对InputStream
中的数据压缩或解压
装璜者模式构造
图片起源:https://refactoringguru.cn/de…
上面总结一下在后面的例子中,各个类和上图中的对应关系:
- 部件(Component)对应有
HttpServletRequest
、List
、InputStream
- 根底装璜(Base Decorator)对应有
HttpServletRequestWrapper
、FilterInputStream
; - 具体装璜类(Concrete Decorators)对应有
Servlet3SecurityContextHolderAwareRequestWrapper
、UnmodifiableList
、DataInputStream
关注我的公众号
微信搜一搜 Java 论道 关注我