套娃想必大家都不陌生。就是同一种娃娃,有大有小,然后一层层嵌套。在设计模式中,有一种常用的套娃模式,称为装饰器(Decorator)模式,也称为包装器(Wrapper)模式。HttpServletRequestMatryoshka在Spring框架开发的Web应用中,如果使用SpringSecurity或者SpringSession,使用Debug模式观察某个请求对应的HttpServletRequest对象,会发现这是一个俄罗斯套娃:可以在图中我们拿HttpServletRequest对象来说,内部成员包含一个HttpServletRequest对象,内部的HttpServletRequest对象又包含一个HttpServletRequest对象,层层叠加。这是一个典型的装饰者模式。我们知道HttpServletRequest是Servlet规范中提供的接口接口。Servlet规范本身并没有实现HttpServletRequest接口,HttpServletRequest接口一般由Servlet容器实现,比如Tomcat、Jetty。SpringSecurity、SpringSession等框架如果想在不改变原有对象接口的情况下,增强HttpServletRequest对象的功能,最好的方式就是使用装饰器模式。例如:SpringSecurity增强了HttpServletRequest.getRemoteUser()方法,可以通过SpringSecurity框架返回当前登录用户的用户名;SpringSession增强了HttpServletRequest.getSession()方法,增强的Session替代了Servlet容器的默认实现。读写可以使用一个中心化的存储,比如Redis,这样可以方便集群内多个实例之间session的共享。HttpServletRequestWrapper/ServletRequestWrapper在javax.servlet.http包下有一个HttpServletRequestWrapper类[源码],它继承自ServletRequestWrapper类[源码]。可以看到这两个类的注释:这个类实现了Wrapper或者Decorator模式。方法默认调用包装的请求对象。翻译:该类实现了装饰器模式/包装器模式,方法模式会直接调用内部包装好的请求对象。ServletRequestWrapper本身实现了ServletRequest接口。它的构造方法需要传入另外一个ServletRequest对象,并将这个对象赋值给内部的request对象:publicclassServletRequestWrapperimplementsServletRequest{publicServletRequestWrapper(ServletRequestifrequest=request){=null){thrownewIllegalArgumentException("Requestcannotbenull");}this.request=请求;}//...}ServletRequestWrapper通过直接调用内部请求对象对应的方法来实现ServletRequest接口方法:this.request.getInputStream();}publicStringgetParameter(Stringname){returnthis.request.getParameter(name);}//...以上是最基本的装饰器。我们可以直接使用娃娃:HttpServletRequestrequest=...;//已有的请求对象HttpServletRequestrequestWrapper=newHttpServletRequestWrapper(request);//Wrappedobject当然上面的代码没有任何意义,因为requestWrapper并没有做任何扩展,使用requestWrapper对象和直接使用request对象没有区别。真正的装饰器类会继承ServletRequestWrapper,并在此基础上进行增强。接下来我们看看SpringSecurity和SpringSession是如何装饰HttpServletRequest对象的。SpringSecurity/SpringSession中的装饰器是在SpringSecurity文档ServletAPI集成中实现的。可以看到SpringSecurity框架对HttpServletRequest对象的getRemoteUser()、getUserPrincipal()、isUserInRole(String)等方法进行了增强,比如getRemoteUser()方法可以直接返回当前登录用户的用户名。让我们看看SpringSecurity是如何增强这些方法的。首先,SpringSecurity提供了一个过滤器SecurityContextHolderAwareRequestFilter来过滤相关的请求。在SecurityContextHolderAwareRequestFilter的第149行结合HttpServlet3RequestFactory的第163行,我们可以看到在这个Filter中新建了一个Servlet3SecurityContextHolderAwareRequestWrapper对象,它继承了HttpServletRequestWrapper类并增强了相关方法。它的父类SecurityContextHolderAwareRequestWrapper类【源码】可以看到getRemoteUser()方法的增强:==空|(auth.getPrincipal()==null)){返回null;}if(auth.getPrincipal()instanceofUserDetails){return((UserDetails)auth.getPrincipal()).getUsername();}if(authinstanceofAbstractAuthenticationToken){returnauth.getName();}返回auth.getPrincipal().toString();}//...}简单的说,SpringSecurity通过一个Filter过滤相关请求,得到原始的HttpServletRequest对象,并通过一个继承自HttpServletRequestWrapper类的装饰器,对getRemoteUser()等相关方法进行增强,然后将增强后的对象交给后续的业务处理,那么后续我们在Controller层获取到的HttpServletRequest对象就可以直接使用getRemoteUser()等方法。SpringSession的实现与SpringSecurity类似,这里不再赘述。有兴趣的可以看看SessionRepositoryFilter的源码。集合中的装饰器装饰器模式不仅增强了被装饰对象的功能,也禁用了某些功能。当然,禁用实际上是一种“增强”。例如,假设有一个List,当我们需要将这个List传递给第三方方法进行读取,但是由于第三方方法是不可信任的,为了防止这个方法篡改List,修改可以通过装饰器模式方法禁用列表,将其装饰为只读列表。java.util.Collections提供了一个静态方法unmodifiableList(List)用于将一个List封装成一个只读的List:List
