版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
英文原文地址:
http://dev2dev.bea.com/pub/a/2005/05/decorators.html
中文地址
http://www.matrix.org.cn/resource/article/43/43603_Servlet_Request.html
关键词:Servlet Requestfilter Decorator
摘要
装饰模式是Erich Gamma等人所著的《设计模式:可利用面向对象软件的基础》一书中众多模式之一。一般来说,此模式在设计Swing的程序员中比较流行,他们用它来改进软件。今天,即使有许多程序是基于Web应用的,装饰模式仍有用武之地,在J2EE的环境下也有使用的价值。
本文说明了如何将装饰模式应用到servlet request对象上。首先,提出了一个与servlet filter有关的问题,并解释了随之而引入的装饰模式。然后,讨论了如何在servlet环境下使用此模式,并列出了使用此模式的几个比较有名的基于servlet的项目。最后,文章通过实现一个删除空白符的filter例子,演示了装饰模式在servlet中的使用。
简介
Servlet规范中所引入的filter令人心动不已,因为它引入了一个功能强大的拦截模式。Filter是这样一种Java对象,它能在request到达servlet的服务方法之前拦截HttpServletRequest对象,而在服务方法转移控制后又能拦截HttpServletResponse对象。你可以使用filter来实现特定的任务,比如验证用户输入,以及压缩web内容。但你拟富有成效地使用过滤器的念头却被你不能改变HttpServletRequest对象的参数的现实扫了兴,因为java.util.Map所包装的HttpServletRequest对象的参数是不可改变的。这极大地缩减了filter的应用范围。至少在一半的时间里,你希望可以改变准备传送给filter的对象。如果在HttpServletRequest对象到达Struts的action servlet之前,我们可以通过一个filter将用户输入的多余空格去掉,难道不是更美妙吗?这样的话,你就不必等到在Struts的action表单验证方法中才进行这项工作了。
幸运的是,尽管你不能改变不变对象本身,但你却可以通过使用装饰模式来改变其状态。
装饰模式
在继承中,你可以通过继承一个父类并覆盖你希望改变的方法来改变对象状态。然而,如果这个对象是由程序的另一个子模块,例如对象工厂 (这里所说的工厂是工厂模式中的术语,下同。译者注) 或是servlet容器所产生的,继承就无能为力了。
装饰模式可用来增加一个现有对象的功能,或是改变其状态。与其使用继承方式来扩展此类,这个模式将一个对象包装成另外一个对象。图1是装饰模式的UML类图。
图1:装饰模式
在图1中,Component是一个接口,其具体实现是ConcreteComponent。要改变Component的状态,你可以修改ConcreteComponent或是扩展它 (通过继承或实现接口的方式,译者注)。然而,如果ConcreteComponent来自于一个工厂,你却无计可施。你所能做的,就是创建一个同为实现了Component接口的装饰类。在图1中,这个装饰类的角色就由Decorator来扮演,在程序中通常表现为接口或抽象类。Decorator类的一个特性就是,它有一个接收Component对象的构造方法。你将拟装饰的对象传递给这个构造方法。在本例中,这个对象就是从工厂获得的ConcreteComponent对象。通过将此装饰对象传递给Decorator的一个类变量,你可以访问Decorator中的任何方法。这就使你得以改变对象的状态了。
图1中的Decorator类不一定是接口或抽象类。如果你的程序不是很复杂,你可以将其转化为一个具体的Decorator类。
举个例子,考虑这样一个简单的消息传递程序,其主要部分是Messenger接口及其实现类MessengerImpl。让我们假设MessengerImpl对象来自于一个工厂,因此你不能改变其状态。如果你准备增加或改变Messenger对象的功能,你可以创建一个MessengerDecorator类。图2是此例子的类图。

图2:Messenger装饰类
我们来看程序的代码。列表1给出了Messenger接口的代码,列表2是MessengerImpl类的代码。
列表1:Messenger接口public interface Messenger {
public String getMessage();
}
列表2:MessengerImpl类public class MessengerImpl implements Messenger {
private String message;
public MessengerImpl(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
Messenger对象由一个名为MessengerFactory的工厂创建,如列表3所示。
列表3:MessengerFactory类public class MessengerFactory {
public static Messenger getMessenger() {
return new MessengerImpl("secrets");
}
}
对每一个所创建的Messenger对象,此工厂通过某个未知的操作,初始化了getMessage()方法所返回的字符串。换句话说,你不能自己创建Messenger对象。
在程序中,Messenger对象的主要用途是被传递给一个名为Util的类中的broadcast()静态方法。列表4是Util类的代码。
列表4:Util类public class Util {
public static void broadcast(Messenger messenger) {
System.out.print(messenger.getMessage());
}
// other methods here
}
在你自己的类中,你可能会有这样的代码:
Messenger messenger = MessengerFactory.getMessenger();
Util.broadcast(messenger);
假设你希望对broadcast()方法所打印出的消息做一小改动。你拟将其转为大写,怎么做?表面上看,你可以继承Messenger,实例化其子类,并将返回的对象传给Util.broadcast()。但是,这种做法毫无意义,因为只有工厂才知道如何初始化Messenger对象,并通过其getMessage()方法返回正确的值。
使用装饰模式,你可以创建一个MessengerDecorator类,如列表5所示。
列表5:MessengerDecorator类public class MessengerDecorator implements Messenger {
private Messenger messenger;
public MessengerDecorator(Messenger messenger) {
this.messenger = messenger;
}
public String getMessage() {
return messenger.getMessage().toUpperCase();
}
}
因为MessengerDecorator实现了Messenger,Util.broadcast()将接受一个MessengerDecorator的实例。然而,MessengerDecorator不仅仅是一个接口的实现,它还是一个MessengerImpl对象的装饰器。正因如此,MessengerDecorator就必须有一个接收拟被装饰的Messenger对象的构造方法。
如列表5所示,这个构造方法将参数传给变量。你现在可以覆盖MessengerDecorator中的getMessage()方法,以便将消息转为大写后再打印出来。因为你持有原来Messenger对象的引用,你可以这样写getMessage()方法:
public String getMessage() {
return this.messenger.getMessage().toUpperCase();
}
MessengerDecorator中的getMessage()方法返回原始消息的大写版本。
在你的类中,就像往常一样,你得到一个Messenger对象,并将Decorator传给Util.broadcast()。
Messenger messenger = factory.getMessenger();
Util.broadcast(new MessengerDecorator(messenger));
你并不将原始对象传给原先的目标,相反,你将其传给了该对象的装饰器。
应用装饰模式于Servlet以上Messenger类的例子与servlet容器所构造的ServletRequest对象是一样的。当收到一个HTTP请求时,servlet容器就会创建ServletRequest对象及ServletResponse对象(分别是ServletRequestImpl及ServletResponseImpl的实例),并将这两个对象传递给特定的servlet服务方法。现在,如果你为ServletRequest创建一个装饰角色,并将其传给servlet服务方法,你就应用了装饰模式。
对ServletRequest很容易应用装饰模式,因为servlet API已经为其提供了一个包装类:ServletRequestWrapper。图3是一个servlet装饰模式的类图。
图3:Servlet API中的装饰模式
图3中的HTTP版本的类图如图4所示。别为过多的类搞晕了头,只管注意虚线框中的三个类就行了:HttpServletRequest, HttpServletRequestImpl, HttpServletRequestWrapper。
图4:Servlet API (HTTP)的装饰模式
情况与前面所举例子类似。你拥有一个ServletRequest的实现,而它是由servlet容器产生的。你可以使用所提供的ServletRequestWrapper来装饰这些ServletRequest对象。
这个模式很简单,在实际应用中可以派上用场。实际上,一些很有名的应用就使用了此模式。这些应用包括:
Struts - Struts是当前开发Java Web应用最受欢迎的基于MVC(模型-视图-控制)模式的框架。Struts提供了相当于ServletRequest包装类的org.apache.struts.upload.MultipartRequestWrapper类。 MultipartRequestWrapper覆盖了getParameter(),getParameterNames(),及getParameterValues()等方法来实现文件上传。
Apache Beehive ?C 这个源于BEA的WebLogic专题小组的开源项目,构建于Struts之上,并简化了web应用及web服务的开发。与ServletRequest包装类一样,org.apache.beehive.netui.pageflow.internal包中的PageFlowRequestWrapper类有助于任意Apache Beehive应用的页流处理。
现在,让我们来看看,如何编写自己的HttpServletRequest装饰类。
一个删除空白字符的Filter本节将以上的理论投入实际使用,通过实现一个删除空白字符的filter,来演示如何使用javax.servlet.http.HttpServletRequestWrapper类来装饰HttpServletRequest对象。在本例中,这个filter将删除所传来的参数中多余的空白字符。
这在许多servlet/JSP应用中是很有用的,包括Struts及JavaServer Faces等应用。例如,Struts通过调用HttpServletRequest对象的getParameterValues()对象来处理action表单。通过覆盖装饰类中此方法,你可以改变当前HttpServletRequest对象的状态。
要创建HttpServletRequest的装饰类,你需要继承HttpServletRequestWrapper并且覆盖你希望改变的方法。列表5中,MyRequestWrapper类将删除getParameterValues()方法返回值的多余空白字符。
列表5:HttpServerletRequest装饰类package trimmer.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public final class MyRequestWrapper extends
HttpServletRequestWrapper {
public MyRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
public String[] getParameterValues(String parameter) {
String[] results = super.getParameterValues(parameter);
if (results==null)
return null;
int count = results.length;
String[] trimResults = new String[count];
for (int i=0; i<count; i++) {
trimResults = results[i].trim();
}
return trimResults;
}
}
列表6演示了如何载获Http请求并装饰HttpServletRequest对象。
[i]列表6:删除空白符的filter
列表6演示了如何载获Http请求并装饰HttpServletRequest对象。
[i]列表6:删除空白符的filter
package trimmer.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class MyFilter implements Filter {
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws
ServletException {
System.out.println("Filter initialized");
this.filterConfig = filterConfig;
}
public void destroy() {
System.out.println("Filter destroyed");
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
chain.doFilter( new MyRequestWrapper((HttpServletRequest) request), response);
}
}
这个程序使用了列表6所示的filter来修整用户输入。要使用这个filter,你需要在web.xml文件中如下设置filter及filter-mapping的元素。
<filter>
<filter-name>TrimmerFilter</filter-name>
<filter-class>trimmer.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TrimmerFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
要测试这个filter,启动这个应用后,在表单中输入一些值,提交表单,看看这个filter是如何修整输入数值的。这是一个实用的装饰模式的应用。
小结Servlet filter可以在调用一个servlet的服务方法后,拦载或加工HTTP请求。尽管这非常诱人,但其实际使用却有所限制,因为你不能改变HttpServletRequest对象。
这时候装饰模式派上了用场。本文演示了如何通过应用装饰模式来“修改”HttpServletRequest对象,从而使你的servlet filter更加有用。在上面filter例子中,filter改了request参数中的用户输入,而这一点,如果没有装饰request对象,你是无论如何也不可能做到的。
Budi Kurniawan是一个高级J2EE的架构师。他还是《Tomcat如何工作:教你如何开发自己的Servlet容器》 (”How Tomcat Works: A Guide to Developing Your Own Servlet Container”) 以及《Struts设计与编程指南》(”Struts Design and Programming :A Tutorial”) 这两本书的作者,它们均由BrainySoftwar.com出版。
分享到:
相关推荐
在Servlet框架中,装饰模式能够解决特定问题,例如处理HttpServletRequest对象。 **问题与解决方案** Servlet Filter是一个强大的工具,可以在请求到达Servlet之前或之后进行拦截操作,如用户验证和内容压缩。然而...
装饰模式是一种设计模式,它允许我们向一个现有的对象添加新的行为或责任,而无需修改该对象的源代码。在JavaWeb中,装饰HttpServletRequest对象可以用来实现诸如日志记录、安全检查、性能监控等功能,而不影响原有...
- **Request转发**:通过调用`request.getRequestDispatcher().forward(request, response)`,一个Servlet可以将请求转发给另一个Servlet处理。 - **URL重定向**:使用`response.sendRedirect()`,Servlet可以引导...
redirect就是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以session,request参数都可以获取。 23、EJB与JAVA BEAN的区别? Java Bean 是可...
5. **Request对象**:主要方法包括getAttribute(), setAttribute(),getParameter(),getParameterValues()等,用于获取和设置请求参数。 6. **会话跟踪技术**:除了Session和Cookie,还有URL重写和隐藏表单字段等方式...
理解JSP的九大内置对象,如request、response、session、application等,以及EL(Expression Language)和JSTL(JavaServer Pages Standard Tag Library)的使用,能够让你在处理动态页面时更加得心应手。...
装饰器模式是一种结构型设计模式,它允许向对象添加新的功能而无需修改其结构。通过这种方式,可以在运行时动态地扩展一个对象的行为。 - **优点**:提高系统的灵活性,可以在不影响其他对象的情况下扩展对象的功能...
- **装饰者模式**:`Wrapper`类链式结构用于装饰Servlet,提供额外的功能。 - **观察者模式**:例如在`Lifecycle`接口和`LifecycleListener`接口中,用于监听容器状态变化并作出相应。 - **责任链模式**:`...
5. 题目5:在Servlet中,session.setAttribute("key","value")用于在session对象中保存属性,答案是B。 6. 题目6:静态变量属于类,而非对象,所有实例共享同一个静态变量。因此,无论创建多少个对象,x的值只有一...
2. JSP内置对象:page、request、response、session、application、out、exception等,各有其作用。 3. session与cookie:session用于服务器端存储用户状态,cookie用于客户端存储。 七、异常处理 1. throw和...
- **Processor线程池**:每个Processor从Socket读取请求,解析为Request对象,然后与Response对象一起封装成WorkItem,提交给Executor执行。 5. **部署与加载** - **Web应用部署**:Tomcat支持WAR文件和解压后的...
- `public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException`:处理客户端请求的核心方法。 - `public void destroy()`:在Servlet实例销毁前调用,用于...
CORBA(Common Object Request Broker Architecture)是跨平台的分布式对象计算架构,用于不同系统间的对象交互。 **LINUX方面** 了解基本的Linux命令、文件系统、进程管理、网络配置等,对于开发和部署Java应用...
`${requestScope.productID}`:`requestScope`隐式对象则直接访问请求作用域中的属性。 SCWCD认证考试涵盖了JSP、Servlet、EL、JSTL、过滤器、监听器、MVC架构等多个主题,这些知识点对于开发高质量的Java EE Web...
10. 题目10:类图所示的设计模式符合装饰模式(Decorator),它允许在运行时动态地给对象添加新的行为或职责。 11. 题目11:在Java中,获取request域中的参数应使用request.getParameter()方法,答案A。 这些题目...
值得注意的是,`HttpServletRequestWrapper` 是 `HttpServletRequest` 的装饰类,它允许我们在不修改原始请求对象的情况下添加新的功能或行为。在上述例子中,`HttpServletRequestWrapper` 没有直接影响获取URI的...
10. **装饰模式**:在不改变对象自身的基础上,动态地给对象添加新的行为或属性。 11. **享元模式**:使用共享技术有效地支持大量细粒度的对象。 12. **代理模式**:为其他对象提供一种代理以控制对这个对象的访问...
redirect就是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以session,request参数都可以获取。 20、EJB与JAVA BEAN的区别? Java Bean 是可复用...
熟悉request对象的方法,如getParameter、setAttribute等,session管理和ServletContext。了解Filter和web.xml配置,以及Tomcat服务器。理解HTTP协议的无状态性,以及cookie和JSP作用域对象。 9. **HTML与...
4. **过滤器链中调用方法的作用**:过滤器在调用它接收到的对象(实现了`javax.servlet.FilterChain`接口的对象)的方法时,该方法可以调用另一个过滤器或Servlet。这是正确的,通过调用`FilterChain`对象的`...