分类: 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有关。
- import javax.servlet.*;
- import javax.servlet.http.*;
- class testServlet extends HttpServlet
- {
- public void doGet(HttpServletRequest req,HttpServletResponse resp)
- throws ServletException,java.io.IOException
- {
- resp.setContentType("text/html; charset=GB2312");
- java.io.PrintWriter out=resp.getWriter();
- out.println("");
- out.println("#中文#");
- out.println("");
- }
- }
该文件也是用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容器对象。
- WebApplicationContext webApplicationContext = WebApplicationContextUtils.
getWebApplicationContext(request.getSession().getServletContext());
所以可以直接在Servlet Context取出Web Application Context对象:
- WebApplicationContext webApplicationContext = (WebApplicationContext)
servletContext.getAttribute(WebApplicationContext.
ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
事实上Web Application ContextUtils.getWebApplicationContext方法就是使用上面的代码实现的,建议使用上面上面的静态方法
注意:在使用web Application Context.getBean("ServiceName")的时候,前面强制转化要使用接口,如果使用实现类会报类型转换错误。如:
- 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
- import javax.servlet.http.*;
- import javax.servlet.*;
- public class OnLineCountListener implements HttpSessionListener,
- ServletContextListener,ServletContextAttributeListener
- {
- private int count;
- private ServletContext context = null;
- public OnLineCountListener()
- {
- count=0;
- //setContext();
- }
- //创建一个session时激发
- public void sessionCreated(HttpSessionEvent se)
- {
- count++;
- setContext(se);
- }
- //当一个session失效时激发
- public void sessionDestroyed(HttpSessionEvent se)
- {
- count--;
- setContext(se);
- }
- //设置context的属性,它将激发attributeReplaced或attributeAdded方法
- public void setContext(HttpSessionEvent se)
- {
- se.getSession().getServletContext().
- setAttribute("onLine",new Integer(count));
- }
- //增加一个新的属性时激发
- public void attributeAdded(ServletContextAttributeEvent event) {
- log("attributeAdded('" + event.getName() + "', '" +
- event.getValue() + "')");
- }
- //删除一个新的属性时激发
- public void attributeRemoved(ServletContextAttributeEvent event) {
- log("attributeRemoved('" + event.getName() + "', '" +
- event.getValue() + "')");
- }
- //属性被替代时激发
- public void attributeReplaced(ServletContextAttributeEvent event) {
- log("attributeReplaced('" + event.getName() + "', '" +
- event.getValue() + "')");
- }
- //context删除时激发
- public void contextDestroyed(ServletContextEvent event) {
- log("contextDestroyed()");
- this.context = null;
- }
- //context初始化时激发
- public void contextInitialized(ServletContextEvent event) {
- this.context = event.getServletContext();
- log("contextInitialized()");
- }
- private void log(String message) {
- System.out.println("ContextListener: " + message);
- }
- }
在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