当前位置: 首页 > 后端技术 > Java

设计模式中的套娃:装饰者模式

时间:2023-04-01 15:37:35 Java

套娃想必大家都不陌生。就是同一种娃娃,有大有小,然后一层层嵌套。在设计模式中,有一种常用的套娃模式,称为装饰器(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:Listlist=...;ListunmodifiableList=Collections.unmodifiableList(list);从这个方法的源码可以看出,Collections.unmodifiableList(List)方法实际上返回的是一个UnmodifiableList。UnmodifiableList是一个典型的装饰器,对于List的读相关方法直接调用被装饰对象对应的方法,写相关方法进行限制,抛出UnsupportedOperationException。下面是UnmodifiableList的部分代码:staticclassUnmodifiableListextendsUnmodifiableCollectionimplementsList{finalList列表;UnmodifiableList(Listlist){super(list);this.list=列表;}publicEget(intindex){returnlist.get(index);}publicEset(intindex,Eelement){thrownewUnsupportedOperationException();}publicvoidadd(intindex,Eelement){thrownewUnsupportedOperationException();}publicEremove(intindex){thrownewUnsupportedOperationException();}publicintindexOf(Objecto){returnlist.indexOf(o);}publicintlastIndexOf(Objecto){returnlist.lastIndexOf(o);}publicbooleanaddAll(intindex,Collectionc){thrownewUnsupportedOperationException();}//...}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)等方法返回类型安全的Collection,如果插入到集合中的元素类型不符合要求,则抛出异常将被抛出。InputStreamdecoratorDecorator不仅可以增强被装饰对象原有的方法,还可以增加新的方法扩展功能。在java.io包中,有inputstreamforInputStream一个基本的抽象装饰器FilterInputStream,其源码如下:publicclassFilterInputStreamextendsInputStream{protectedFilterInputStream(InputStreamin){this.in=in;}publicintread()throwsIOException{returnin.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、InputStreamBaseDecorator对应HttpServletRequestWrapper、FilterInputStream;ConcreteDecorators对应Servlet3SecurityContextHolderAwareRequestWrapper、UnmodifiableList、DataInputStreamI