Chinaunix首页 | 论坛 | 博客
  • 博客访问: 519542
  • 博文数量: 260
  • 博客积分: 10435
  • 博客等级: 上将
  • 技术积分: 1939
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-24 14:50
文章分类

全部博文(260)

文章存档

2011年(22)

2010年(209)

2009年(29)

我的朋友

分类: Java

2010-11-15 10:59:13



接 口是Java Servlet API的中心抽象。所有实现这个接口的servlet可以直接,或通常是扩展一个类,实现这些接口。Java Servlet API中有两个类实现了Servlet接口,他们是GenericServlet和HttpServlet。大多数情况下,开发者可以扩展 HttpServlet,实现它们的servlet。

1.1 请求处理方法

基本的Servlet接口定义了处理客户端请求的service方法。当servlet容器将每个请求路由到servlet实例时,会调用这个方法。

并发的处理web应用程序的请求,通常需要web开发者设计servlet时,在service方法的特定时期处理多线程执行问题。

通常web容器通过在不同线程中并发的执行同一个servlet中的service方法,处理并发请求。

1.1.1 HTTP特定的请求处理方法

HttpServlet抽象类在基本的Servlet接口中添加方法,可以被service方法在处理基于HTTP的请求时自动调用。这些方法是:

l         doGet for handling HTTP GET requests

l         doPost for handling HTTP POST requests

l         doPut for handling HTTP PUT requests

l         doDelete for handling HTTP DELETE requests

l         doHead for handling HTTP HEAD requests

l         doOptions for handling HTTP OPTIONS requests

l         doTrace for handling HTTP TRACE requests

通常在开发基于HTTP的servlet时,servlet开发者只需要关注于doGet和doPost方法。其他方法可以由非常熟悉HTTP编程的人员使用。

1.1.2 其他方法

doPut 和doDelete方法允许servlet开发者支持使用HTTP/1.1中这些特性的客户端。HttpServlet 中的doHead方法是一种特殊的doGet方法,只返回由doGet方法创建的header。doOptions方法响应servlet支持的HTTP 方法。doTrace方法生成一个应答,包含所有在TRACE请求中发送的header。

1.1.3 有条件的GET支持

HttpServlet接口中定义了getLastModified方法,支持有条件的GET操作。有条件的GET操作只有当资源在一段时间之内被修改过,才会发送。在一些情况下,这个方法的实现可以帮助高效的使用网络资源。

1.2 实例的数量

Web应用程序部署描述符中的servlet声明包含servlet,在第十三章,“部署描述符”中介绍,它控制servlet容器如何提供 servlet实例。对于非分布式环境中的servlet(默认情况),servlet容器必须为每个servlet声明只生成一个实例。然而,对于实现 SingleThreadModel接口的servlet,servlet容器可以实现多个实例,处理高压力的请求负载以及串行的请求特定实例。

当 作为应用程序一部分部署的servlet在部署描述符中标记为distributable时,容器必须在一个Java虚拟机(JVM)中只有一个实例。然 而,如果分布式应用程序的servlet实现了SingleThreadModel接口,容器可以在容器的每个JVM中初始化多个servlet实例。

1.2.1 单线程模型的注意事项

使 用SingleThreadModel接口,保证一个时间内,servlet实例的service方法只被一个线程调用。注意,这种保证只应用于每个 servlet实例,因为容器可以选择这些对象的实例池。多余一个servlet实例在一个时间可以访问的对象,例如HttpSession实例,可以在 多个servlet的任何时刻得到,即使实现了SingleThreadModel夜市入词。

建议开发者使用其他方式处理这些问题,而不是使用这个接口,例如避免使用实例变量或在同步块中访问这些资源。SingleThreadModel接口在本版本的规范中已经被废弃。

1.3 Servlet声明周期

Servlet通过良好定义的声明周期,定义了它如何加载和实例化,初始化,处理客户端请求,以及从服务中取出。

声明周期在API中被表示成javax.servlet.Servlet 接口的init, service, 和destroy方法,这样所有的servlet必须直接或间接的通过GenericServlet 或HttpServlet实现它。

1.3.1 加载和初始化

Servlet容器负责加载和初始化servlet。加载和初始化可以在容器开始时进行,或者延迟到容器决定servlet需要服务请求时。

当servlet引擎启动后,servlet容器必须加载需要的servlet类。Servlet容器使用普通的Java类加载机制加载servlet类。加载可以从本地文件系统中进行,或者远程文件系统,或者其他网络服务器。

当servlet类加载结束后,容器会初始化它。

1.3.2 初始化

在 servlet对象实例化之后,容器必须在它处理客户端请求之前对它进行初始化。在初始化过程中,servlet可以读取持久化配置数据,初始化昂贵的资 源(例如基于JDBC API的连接),同时执行其他一次性的活动。容器通过调用Servlet接口的init方法初始化servlet实例,以一个唯一的(对于每个 servlet声明来说)实现了ServletConfig的对象为参数。

Configuration对象允许servlet访问web 应用程序配置信息中的name-value初始化参数。Configuration对象同时允许servlet访问servlet的运行时环境中介绍的对 象(实现了ServletContext接口)。关于ServletContext更详细的信息,请参考第三章“servlet上下文”。

1.3.2.1 初始化过程中的错误条件

在初始化过程中,servlet实例或抛出UnavailableException或ServletException。这种情况下,servlet不能被放置在活动的服务中,并且必须由servlet容器释放。容器不会调用destroy方法,因为它没有成功初始化。

初始化失败后,容器可以在实例化和初始化一个新的实例。这种规则的例外是,当UnavailableException指定了一小段的不可用时间,同时容器必须在创建和初始化新的servlet实例之前等待一段时间。

1.3.2.2 工具考虑

当 工具加载和introspect web应用程序时,调用静态初始化方法的过程和调用init方法是不同的。开发者不能假设servlet是一个活动的容器运行时,直到Servlet接口 的init方法调用后。例如,当静态初始化方法被调用时,servlet不应该尝试建立数据库或企业bean容器连接。

1.3.3 请求处理

当 请求合适的初始化之后,servlet容器可以使用它处理客户端请求。请求由ServletRequest类型的request对象表示。Servlet 通过调用ServletResponse对象提供的方法填充响应信息。这些对象都作为Servlet接口中service方法的参数进行传递。

在HTTP请求中,容器提供的对象类型是HttpServletRequest和HttpServletResponse。

注意,servlet容器放置到服务中的实例,可以在生命周期中不处理任何请求。

1.3.3.1 多线程问题

Servlet容器可以通过servlet的service方法并发的发送请求。要处理这些请求,servlet开发者必须为service方法在多个线程中并发执行提供足够的条件。

另 一种不推荐的方式是实现SingleThreadModel接口,这样容器可以保证同一个时间只有一个线程请求service方法。Servlet容器可 以通过串行servlet请求满足这个需求,也可以维护servlet实例池。如果servlet是web应用程序的一部分,并且已经被标记为 SingleThreadModel,那么容器可以在每个应用程序分布式访问的JVM中维护servlet的实例池。

对于没有实现 SingleThreadModel接口的servlet,如果service方法(或者由HttpServlet抽象类的service方法分发的 doGet或doPost这类方法)已经使用synchronized关键字进行定义,这时servlet容器不使用实例池方案,但不许串行的请求它。强 烈建议开发者不要同步service方法(或者这个方法转发的方法),这样会对性能造成不利的影响。

1.3.3.2 请求过程中的异常处理

Servlet可以在服务请求时抛出ServletException 或者UnavailableException。ServletException表示请求除了的过程中出现了错误,容器应该使用合适的方式清理请求。

UnavailableException表示servlet不能处理请求,可以是暂时的,也可以是永久的。

如果UnavailableException表示永久的不可用,那么servlet容器必须从服务中移除servlet,调用它的destroy方法,同时释放servlet实例。由于这个原因容器拒绝的任何请求,必须返回SC_NOT_FOUND (404)应答。

如 果UnavailableException表示暂时的不可用,容器可以选择在servlet暂时不可用的一段时间内,不向它路由任何请求。这段时间内任 何被容器拒绝的请求,必须使用SC_SERVICE_UNAVAILABLE (503)进行应答,同时的Retry-After header,表示不可用的截止时间。

容器可以忽略永久和暂时不可用的区别,将所有的UnavailableExceptions都当作永久的,因此将任何service中抛出UnavailableException的servlet移除。

1.3.3.3 线程安全

Request和response对象的实现不保证线程安全。这意味着它应该只在请求处理线程的作用域中使用。

Request 和response对象的引用不应该给予其他线程中执行的对象,这样会导致行为的不确定性。如果应用程序创建的线程使用了容器管理的对象,例如 request或response对象,这些对象必须只在servlet的service声明周期中访问,同时这些线程的声明周期必须处于servlet 的service方法的声明周期之内,因为当service方法结束后,再访问这些对象,可能导致不确定的错误。记住,request和response 对象不是线程安全的。如果这些对象被多个线程访问,访问必须序列化,或者通过添加线程安全的包装类执行,例如,序列化调用访问request属性的方法, 或者在线程中为response使用本地输出流。

1.3.3.4 结束服务

Servlet容器不需要在任何特定的时期保证servlet被加载。Servlet实例可以在servlet容器中的一段毫秒内保持活动,或者和servlet容器的声明周期一样(可以是几天,几个月或几年),或者任何之间的时间。

当servlet容器决定要将servlet从服务中移除时,会调用Servlet接口的destroy方法,允许servlet释放任何它使用的资源,以及保存任何持久化状态。例如,容器可以在需要保存内存资源时这样做,或者当它被关闭时。

在servlet容器调用destroy方法之前,必须让所有并发执行servlet的service方法的线程执行结束,或者超过服务器定义的时间限制。

一旦servlet实例中调用destroy方法后,容器不吭将其他请求路由到这个实例。如果容器需要再次启用servlet,必须实例化一个新的servlet类。

当destroy方法执行结束后,servlet容器必须释放servlet实例,这样可以进行垃圾收集。


Servlet源文件到Class的过程

Servlet源文件是以“.java”结尾的文本文件。本节将讨论Servlet的编译过程并跟踪其中的中文变化。

用“javac”编译Servlet源文件。javac可以带“-encoding < Compile-charset>”参数,意思是“用<  Compile-charset >中指定的编码来解释Serlvet源文件”。

源文件在编译时,用< Compile-charset>来解释所有字符,包括中文字符和ASCII字符。然后把字符常量转变成Unicode字符,最后,把Unicode转变成UTF。

在Servlet中,还有一个地方设置输出流的CharSet。通常在输出结果前,调用HttpServletResponse的 setContentType方法来达到与在JSP中设置< Jsp-charset>一样的效果,称之为< Servlet-charset>。

注意,文中一共提到了三个变量:< Jsp-charset>、< Compile-charset>和< Servlet-charset>。其中,JSP 文件只与< Jsp-charset>有关,而< Compile-charset>和< Servlet-charset>只与Servlet有关。

  1. import javax.servlet.*;  
  2. import javax.servlet.http.*;  
  3. class testServlet extends HttpServlet  
  4. {  
  5. public void doGet(HttpServletRequest req,HttpServletResponse resp)  
  6. throws ServletException,java.io.IOException  
  7. {  
  8. resp.setContentType("text/html; charset=GB2312");  
  9. java.io.PrintWriter out=resp.getWriter();  
  10. out.println("");  
  11. out.println("#中文#");  
  12. out.println("");  
  13. }  

该文件也是用UltraEdit for Windows编写的,其中的“中文”两个字保存为“D6 D0 CE C4”(GB2312编码)。

开始编译。下表是< Compile-charset>不同时,CLASS文件中“中文”两字的十六进制码。在编译过程中,< Servlet- charset>不起任何作用。< Servlet-charset>只对CLASS文件的输出产生影响,实际上是< Servlet-charset>和< Compile-charset>一起,达到与JSP文件中的< Jsp-charset>相同的效果,因为< Jsp-charset>对编译和 CLASS文件的输出都会产生影响。

“中文”从Servlet源文件到Class的转变过程

Compile-charset Servlet源文件中 Class文件中 等效的Unicode码
GB2312 D6 D0 CE C4
(GB2312) E4 B8 AD E6 96 87 (UTF) \u4E2D\u6587 (在Unicode中=“中文”)
ISO-8859-1 D6 D0 CE C4
(GB2312) C3 96 C3 90 C3 8E C3 84 (UTF) \u00D6 \u00D0 \u00CE \u00C4 (在D6 D0 CE C4前面各加了一个00)
无(默认) D6 D0 CE C4 (GB2312) 同ISO-8859-1 同ISO-8859-1

普通Java程序的编译过程与Servlet完全一样。

CLASS文件中的中文表示法是不是昭然若揭了?OK,接下来看看CLASS又是怎样输出中文的呢?




Servlet容器启动创建了许多对象,如Servlet,filter,listener,spring等等那么如何使用这些对象呢?

下面介绍在Servlet(或者Filter,或者Listener)中使用spring的IOC容器默认情况下Servlet容器创建 spring容器对象,注入到Servlet Context中,Servlet Context对象又是注入到session对象中,session对象又是注入到request对象中,request对象又是注入到Servlet对 象中,(其实不是很标准的注入,是传参数,或者对属性直接付值)。层层依赖可以得到spring容器对象。

  1. WebApplicationContext webApplicationContext = WebApplicationContextUtils.
    getWebApplicationContext(request.getSession().getServletContext());  

所以可以直接在Servlet Context取出Web Application Context对象:

  1. WebApplicationContext webApplicationContext = (WebApplicationContext) 
    servletContext.getAttribute(WebApplicationContext.
    ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  

事实上Web Application ContextUtils.getWebApplicationContext方法就是使用上面的代码实现的,建议使用上面上面的静态方法

注意:在使用web Application Context.getBean("ServiceName")的时候,前面强制转化要使用接口,如果使用实现类会报类型转换错误。如:

  1. LUserService userService = (LUserService) 
    webApplicationContext.getBean("userService");



Servlet和filter是J2EE开发中常用的技术,使用方便,配置简单,老少皆宜。

估计大多数朋友都是直接配置用,也没有关心过具体的细节,今天遇到一个问题,上网查了Servlet的规范才发现,Servlet和filter中的url-pattern还是有一些文章在里面的,总结了一些东西,放出来供大家参考,以免遇到问题又要浪费时间。

一、Servlet容器对url的匹配过程:

当一个请求发送到Servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为Servlet的映射url,比如我访问的是 ,我的应用上下文是test,容器会将去掉, 剩下的/aaa.html部分拿来做Servlet的映射匹配。这个映射匹配过程是有顺序的,而且当有一个Servlet匹配成功以后,就不会去理会剩下 的Servlet了(filter不同,后文会提到)。其匹配规则和顺序如下:

1. 精确路径匹配。例子:比如ServletA 的url-pattern为 /test,ServletB的url-pattern为 /* ,这个时候,如果我访问的url为 ,这个时候容器就会先进行精确路径匹配,发现/test正好被ServletA精确匹配,那么就去调用ServletA,也不会去理会其他的 Servlet了。

2. 最长路径匹配。例子:ServletA的url-pattern为/test/*,而ServletB的url-pattern为/test/a/*,此 时访问/a时,容器会选择路径最长的Servlet来匹配,也就是这里的ServletB.

3. 扩展匹配,如果url最后一段包含扩展,容器将会根据扩展选择合适的Servlet.例子:ServletA的url-pattern:*.action

4. 如果前面三条规则都没有找到一个Servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default Servlet,则容器会将请求丢给default Servlet(什么是default Servlet?后面会讲)。

根据这个规则表,就能很清楚的知道Servlet的匹配过程,所以定义Servlet的时候也要考虑url-pattern的写法,以免出错。

对于filter,不会像Servlet那样只匹配一个Servlet,因为filter的集合是一个链,所以只会有处理的顺序不同,而不会出现只选择一个filter.Filter的处理顺序和filter-mapping在web.xml中定义的顺序相同。

二、url-pattern详解

在web.xml文件中,以下语法用于定义映射:

以“/‘开头和以”/*“结尾的是用来做路径映射的。

以前缀“*.”开头的是用来做扩展映射的。

“是用来定义default Servlet映射的。

剩下的都是用来定义详细映射的。比如: /aa/bb/cc.action

所以,为什么定义“/*.action”这样一个看起来很正常的匹配会错?因为这个匹配即属于路径映射,也属于扩展映射,导致Servlet容器无法判断。




Listener 是Servlet 的监听器,它可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量。当增加一个 Http Session时,就激发session Created(Http Session Event se)方法,这样就可以给在线人数加1。常用的监听接口有以下几个:

Servlet Context Attribute Listener监听对Servlet Context 属性的操作,比如增加、删除、修改属性。

Servlet Context Listener监听Servlet Context 。当创建Servlet Context 时,激发Context Initialized (Servlet Context Event sce)方法;当销毁Servlet Context 时,激发Context Destroyed(Servlet Context Event sce)方法。

Http Session Listener监听Http Session的操作。当创建一个Session时,激发session Created(Http Session Event se)方法;当销毁一个Session时,激发session Destroyed (Http Session Event se)方法。

Http Session Attribute Listener监听Http Session中的属性的操作。当在Session增加一个属性时,激发attribute Added (Http Session Binding Event se) 方法;当在Session删除一个属性时,激发attribute Removed(Http Session Binding Event se)方法;当在Session属性被重新设置时,激发attribute Replaced(Http Session Binding Event se) 方法。

下面我们开发一个具体的例子,这个监听器能够统计在线的人数。在Servlet Context 初始化和销毁时,在服务器控制台打印对应的信息。当Servlet Context 里的属性增加、改变、删除时,在服务器控制台打印对应的信息。

要获得以上的功能,监听器必须实现以下3个接口:
◆HttpSessionListener
◆Servlet Context Listener
◆Servlet Context AttributeListener

  1. import javax.servlet.http.*;  
  2. import javax.servlet.*;  
  3. public class OnLineCountListener implements HttpSessionListener,  
  4. ServletContextListener,ServletContextAttributeListener  
  5. {  
  6. private int count;  
  7. private ServletContext context = null;  
  8. public OnLineCountListener()  
  9. {  
  10. count=0;  
  11. //setContext();  
  12. }  
  13. //创建一个session时激发  
  14. public void sessionCreated(HttpSessionEvent se)  
  15. {  
  16. count++;  
  17. setContext(se);  
  18. }  
  19. //当一个session失效时激发  
  20. public void sessionDestroyed(HttpSessionEvent se)  
  21. {  
  22. count--;  
  23. setContext(se);  
  24. }  
  25. //设置context的属性,它将激发attributeReplaced或attributeAdded方法  
  26. public void setContext(HttpSessionEvent se)  
  27. {  
  28. se.getSession().getServletContext().  
  29. setAttribute("onLine",new Integer(count));  
  30. }  
  31. //增加一个新的属性时激发  
  32. public void attributeAdded(ServletContextAttributeEvent event) {  
  33. log("attributeAdded('" + event.getName() + "', '" +  
  34. event.getValue() + "')");  
  35. }  
  36. //删除一个新的属性时激发  
  37. public void attributeRemoved(ServletContextAttributeEvent event) {  
  38. log("attributeRemoved('" + event.getName() + "', '" +  
  39. event.getValue() + "')");  
  40. }  
  41. //属性被替代时激发  
  42. public void attributeReplaced(ServletContextAttributeEvent event) {  
  43. log("attributeReplaced('" + event.getName() + "', '" +  
  44. event.getValue() + "')");  
  45. }  
  46. //context删除时激发  
  47. public void contextDestroyed(ServletContextEvent event) {  
  48. log("contextDestroyed()");  
  49. this.context = null;  
  50. }  
  51. //context初始化时激发  
  52. public void contextInitialized(ServletContextEvent event) {  
  53. this.context = event.getServletContext();  
  54. log("contextInitialized()");  
  55. }  
  56. private void log(String message) {  
  57. System.out.println("ContextListener: " + message);  
  58. }  

在OnLine Count Listener 里,用count代表当前在线的人数,OnLine Count Listener将在Web服务器启动时自动执行。当 OnLine Count Listener构造好后,把count设置为0。每增加一个Session,OnLine Count Listener会自动调用 session Created(Http Session Event se)方法;每销毁一个Session,OnLine Count Listener会自动调用session Destroyed (Http Session Event se)方法。当调用session Created(Http Session Event se)方法时,说明又有一个客户在请求,此时使在线的人数(count)加1,并且把count写到Servlet Context 中。 Servlet Context 的信息是所有客户端共享的,这样,每个客户端都可以读取到当前在线的人数。

从作用域范围来说,Servlet 的作用域有Servlet Context ,Http Session,Servlet Request.以上是Listener监听Http Session



Filter 技术是servlet 2.3 新增加的功能.servlet2.3是sun公司与2000年10月发布的,它的开发者包括许多个人和公司团体,充分体现了sun公司所倡导的代码开放性原则.由于众多的参与者的共同努力,servlet2.3比以往功能都强大了许多,而且性能也有了大幅提高.
它新增加的功能包括:
1. 应用程序生命周期事件控制;
2. 新的国际化;
3. 澄清了类的装载规则;
4. 新的错误及安全属性;
5. 不赞成使用HttpUtils 类;
6. 各种有用的方法;
7. 阐明并扩展了几个servlet DTD;
8. filter功能.
其中最重要的就是filter功能.它使用户可以改变一个request和修改一个response. Filter 不是一个servlet,它不能产生一 个response,它能够在一个request到达servlet之前预处理request,也可以在离开servlet时处理response.换种 说法,filter其实是一个”servlet chaining”(servlet 链).一个filter 包括:
1. 在servlet被调用之前截获;
2. 在servlet被调用之前检查servlet request;
3. 根据需要修改request头和request数据;
4. 根据需要修改response头和response数据;
5. 在servlet被调用之后截获.
你能够配置一个filter 到一个或多个servlet;单个servlet或servlet组能够被多个filter 使用.几个实用的filter 包括:用户辨认filter,日志filter,审核filter,加密filter,符号filter,能改变xml内容的XSLT filter等.
一个filter必须实现javax.servlet.Filter接口并定义三个方法:
1.void setFilterConfig(FilterConfig config) //设置filter 的配置对象;
2. FilterConfig getFilterConfig() //返回filter的配置对象;
3. void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) //执行filter 的工作.
服务器每次只调用setFilterConfig方法一次准备filter 的处理;调用doFilter方法多次以处理不同的请求.FilterConfig接口有方法可以找到filter名字及初始化参数信息.服务器可以设置 FilterConfig为空来指明filter已经终结.
每一个filter从doFilter()方法中得到当前的 request及response.在这个方法里,可以进行任何的针对request及response的操作.(包括收集数据,包装数据 等).filter调用chain.doFilter()方法把控制权交给下一个filter.一个filter在doFilter()方法中结束.如果 一个filter想停止request处理而获得对response的完全的控制,那它可以不调用下一个filter.
一个filter可以包装request 或response以改变几个方法和提供用户定制的属性.Api2.3提供了HttpServletRequestWrapper 和HttpServletResponseWrapper来实现.它们能分派最初的request和response.如果要改变一个方法的特性,必须继 承wapper和重写方法.下面是一段简单的日志filter用来记录所有request的持续时间.
public class LogFilter implements Filter {
FilterConfig config;

public void setFilterConfig(FilterConfig config) {
this.config = config;
}

public FilterConfig getFilterConfig() {
return config;
}

public void doFilter(ServletRequest req,
ServletResponse res,
FilterChain chain) {
ServletContext context = getFilterConfig().getServletContext();
long bef = System.currentTimeMillis();
chain.doFilter(req, res); // no chain parameter needed here
long aft = System.currentTimeMillis();
context.log("Request to " + req.getRequestURI()
+ ": " + (aft-bef));
}
}
当server调用setFilterConfig(),filter保存config信息.在doFilter()方法中通过config信息得到servletContext.如果要运行这个filter,必须去配置到web.xml中.以tomcat4.01为例:


log //filter 名字


LogFilter //filter class(上例的servlet)



log
servletname


servletname
servletclass


servletname
*


把这个web.xml放到web-inf中(详请参考tomcat帮助文档).
当每次请求一个request时(如index.jsp),先到LogFilter中去并调用doFilter()方法,然后才到各自的servlet中去.如果是一个简单的servlet(只是一个页面,无任何输出语句),那么可能的输出是:
Request to /index.jsp: 10




阅读(448) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~