俄罗斯套娃想必大家都不陌生,就是同一种玩具娃娃大的套小的,然后一层一层嵌套下去。
在设计模式中,有一种常用的套娃模式,叫做装饰者(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论道 关注我
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。