《第2章 Servlet技术模型.ppt》由会员分享,可在线阅读,更多相关《第2章 Servlet技术模型.ppt(83页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第 8 章,Servlet技术模型,本章内容,8.1 Web监听器8.2 Web过滤器8.3 Servlet的多线程问题 8.4 Servlet的异步处理,本章内容,Web应用程序运行过程中可能发生各种事件,如ServletContext事件、会话事件及请求有关的事件等,Web容器采用监听器模型处理这些事件。过滤器用于拦截传入的请求或传出的响应,并监视、修改或以某种方式处理这些通过的数据流。本章主要介绍这两个高级应用,它们是Web事件处理模型和Servlet过滤器。此外,本章还将讨论Servlet多线程问题以及Servlet 3.0的异步处理问题。,8.1 Web监听器,Web应用程序中的事件
2、主要发生在三个对象上:ServletContext、HttpSession和ServletRequest对象。事件的类型主要包括对象的生命周期事件和属性改变事件。例如,对于ServletContext对象,当它初始化和销毁时会发生ServletContextEvent事件,当在该对象上添加属性、删除属性或替换属性时会发生ServletContextAttributeEvent事件。对于会话对象和请求对象也有类似的事件。为了处理这些事件,Servlet容器采用了监听器模型,即需要实现有关的监听器接口。,8.1 Web监听器,在Servlet 3.0 API中定义了7个事件类和9个监听器接口,根据
3、监听器所监听事件的类型和范围,可以把它们分为三类:ServletContext事件监听器HttpSession事件监听器ServletRequest事件监听器,8.1.1 监听ServletContext事件,在ServletContext对象上可能发生两种事件,对这些事件可使用两个事件监听器接口处理,如表8-1所示。,1.处理ServletContextEvent事件,该事件是Web应用程序生命周期事件,当容器对ServletContext对象进行初始化或销毁操作时,将发生ServletContextEvent事件。处理这类事件,需实现ServletContextListener接口,该接口
4、定义如下两方法void contextInitialized (ServletContextEvent sce)当ServletContext对象初始化时调用。void contextDestroyed (ServletContextEvent sce)当ServletContext对象销毁时调用。,1.处理ServletContextEvent事件,上述方法的参数是一个ServletContextEvent事件类对象,该类只定义了一个方法,如下所示。 public ServletContext getServletContext()该方法返回状态发生改变的ServletContext对象,2
5、.处理ServletContextAttributeEvent事件,当ServletContext对象上属性发生改变时,如添加属性、删除属性、替换属性等,将发生ServletContextAttributeEvent事件,处理该类事件,需要实现ServletContextAttributeListener接口。,2.处理ServletContextAttributeEvent事件,该接口定义了如下三个方法。public void attributeAdded(ServletContextAttributeEvent sre):当在ServletContext对象中添加属性时调用该方法。publ
6、ic void attributeRemoved(ServletContextAttributeEvent sre):当从ServletContext对象中删除属性时调用该方法。public void attributeReplaced(ServletContextAttributeEvent sre):当在ServletContext对象中替换属性时调用该方法。,2.处理ServletContextAttributeEvent事件,上述方法的参数是ServletContextAttributeEvent类的对象,它是ServletContextEvent类的子类,它定义了下面三个方法。pub
7、lic ServletContext getServletContext():返回属性发生改变的ServletContext对象。public String getName():返回发生改变的属性名。public Object getValue():返回发生改变的属性值对象。注意,当替换属性时,该方法返回的是替换之前的属性值。,下面程序实现当Web应用启动时就创建一个数据源对象并将它保存在ServletContext对象上,当应用程序销毁时将数据源对象从ServletContext对象上清除,当ServletContext上属性发生改变时登记日志。程序8.1 MyContextListener
8、.java该程序在ServletContextListener接口的contextInitialized()中首先从InitialContext对象中查找数据源对象dataSource并将其存储在ServletContext对象中。在ServletContext的属性修改方法中先通过事件对象的getServletContext()获得上下文对象,然后调用它的log()向日志中写一条消息。,2.处理ServletContextAttributeEvent事件,下面的listenerTest.jsp页面是对监听器的测试,这里使用了监听器对象创建的数据源对象。程序8.2 listenerTest.j
9、sp在该页面中首先通过隐含对象application的getAttribute()得到数据源对象,然后创建ResultSet对象访问数据库。,2.处理ServletContextAttributeEvent事件,8.1.2 监听请求事件,在ServletRequest对象上可能发生两种事件,对这些事件使用两个事件监听器处理,如表8-2所示。,1. 处理ServletRequestEvent事件,ServletRequestEvent是请求对象生命周期事件,当一个请求对象初始化或销毁时将发生该事件,处理该类事件需要使用ServletRequestListener接口如下两个方法:public v
10、oid requestInitialized (ServletRequestEvent sce):当请求对象初始化时调用。public void requestDestroyed (ServletRequestEvent sce):当请求对象销毁时调用。,1. 处理ServletRequestEvent事件,上述方法的参数是ServletRequestEvent类对象,该类定义了下面两个方法:public ServletContext getServletContext():返回发生该事件的ServletContext对象。public ServletRequest getServletReq
11、uest():返回发生该事件的ServletRequest对象。,2. 处理ServletRequestAttributeEvent事件,在请求对象上添加、删除和替换属性时将发生ServletRequestAttributeEvent事件,处理该类事件需要使用ServletRequestAttributeListener接口,它定义了如下三个方法。public void attributeAdded(ServletRequestAttributeEvent src):当在请求对象中添加属性时调用该方法。public void attributeRemoved (ServletRequestAt
12、tributeEvent src):当从请求对象中删除属性时调用该方法。public void attributeReplaced (ServletRequestAttributeEvent src):当在请求对象中替换属性时调用该方法。,在上述方法中传递的参数为ServletRequestAttributeEvent类的对象,该类定义了下面两个方法。public String getName():返回在请求对象上添加、删除或替换的属性名。public Object getValue():返回在请求对象上添加、删除或替换的属性值。注意,当替换属性时,该方法返回的是替换之前的属性值。,2. 处理
13、ServletRequestAttributeEvent事件,下面的MyRequestListener监听器类监听对某个页面的请求并记录自应用程序启动以来被访问的次数。程序8.3 MyRequestListener.java下面是一个测试JSP页面:程序8.4 onlineCount.jsp,2. 处理ServletRequestAttributeEvent事件,8.1.3 监听会话事件,在HttpSession对象上可能发生两种事件,对这些事件可使用四个事件监听器处理,这些类和接口如表8-3所示。,1. 处理HttpSessionEvent事件,HttpSessionEvent事件是会话对象
14、生命周期事件,当一个会话对象被创建和销毁时发生该事件,处理该事件应该使用HttpSessionListener接口,该接口定义了两个方法:public void sessionCreated(HttpSessionEvent se):当会话创建时调用该方法。public void sessionDestroyed(HttpSessionEvent se):当会话销毁时调用该方法。上述方法的参数是一个HttpSessionEvent类对象,该类中只定义了一个getSession(),它返回状态发生改变的会话对象,格式如下。 public HttpSession getSession(),2. 处
15、理会话属性事件,当在会话对象上添加属性、删除属性、替换属性时将发生HttpSessionBindingEvent事件,处理该事件需使用HttpSessionAttributeListener接口,该接口定义了下面三个方法:public void attributeAdded(HttpSessionBindingEvent se):当在会话对象上添加属性时调用该方法。public void attributeRemoved(HttpSessionBindingEvent se):当从会话对象上删除属性时调用该方法。public void attributeReplaced(HttpSession
16、BindingEvent se):当替换会话对象上的属性时调用该方法。注意:上述方法的参数是HttpSessionBindingEvent,没有HttpSessionAttributeEvent这个类。,HttpSessionBindingEvent类中定义了下面三个方法。public HttpSession getSession():返回发生改变的会话对象。public String getName():返回绑定到会话对象或从会话对象解除绑定的属性名。public Object getValue():返回在会话对象上添加、删除或替换的属性值。,2. 处理会话属性事件,下面定义的监听器类实现了
17、HttpSessionListener接口,它用来监视当前所有会话对象。当一个会话对象创建时,将其添加到一个ArrayList对象中并将其设置为ServletContext作用域的属性以便其他资源可以访问。当销毁一个会话对象时,从ArrayList中删除会话。程序8.5 MySessionListener.java程序8.6 sessionDisplay.jsp,2. 处理会话属性事件,3. 处理会话属性绑定事件,当一个对象绑定到会话对象或从会话对象中解除绑定时发生HttpSessionBindingEvent事件,应该使用HttpSessionBindingListener接口来处理这类事件
18、,该接口定义的方法有:public void valueBound(HttpSessionBindingEvent event):当对象绑定到一个会话上时调用该方法。public void valueUnbound (HttpSessionBindingEvent event):当对象从一个会话上解除绑定时调用该方法。,下面定义的User类实现了HttpSessionBindingListener接口。当将该类的一个对象绑定到会话对象上时,容器将调用valueBound(),当从会话对象上删除该类的对象时,容器将调用valueUnbound(),这里向日志文件写入有关信息。程序8.7 User
19、.java程序从HttpSessionBindingEvent对象中检索会话对象,从会话对象中得到ServletContext对象并使用log()登录消息。,3. 处理会话属性绑定事件,下面是一个Servlet,它接受登录用户的用户名和口令,然后创建一个User对象并将其绑定到会话对象上。程序8.8 LoginServlet.java,3. 处理会话属性绑定事件,8.1.4 事件监听器的注册,从前面的例子中可看到,我们使用WebListener注解来注册监听器,这是Servlet 3.0规范增加的功能。事件监听器也可以在DD文件中使用元素注册。该元素只包含一个元素,用来指定实现了监听器接口的完
20、整的类名。下面代码给出了如何注册MyContextListener和MySessionListener两个监听器。 com.listener.MyContextListener com.listener.MySessionListener ,在web.xml文件中并没有指定哪个监听器类处理哪个事件,这是因为当容器需要处理某种事件时,它能够找到有关的类和方法。容器实例化指定的类并检查类实现的全部接口。对每个相关的接口,它都向各自的监听器列表中添加一个实例。容器按照DD文件中指定的类的顺序将事件传递给监听器。这些类必须存放在WEB-INFclasses目录中或者与其他Servlet类一起打包在JA
21、R文件中。提示:可以在一个类中实现多个监听器接口。这样,在部署描述文件中就只需要一个元素。容器就仅创建该类的一个实例并把所有的事件都发送给该实例。,8.1.4 事件监听器的注册,8.2 Web过滤器,8.2.1 什么是过滤器8.2.2 过滤器API8.2.3 一个简单的过滤器8.2.4 WebFilter注解8.2.5 在DD中配置过滤器,8.2.1 什么是过滤器,过滤器(Filter)是Web服务器上的组件,它拦截客户对某个资源的请求和响应,对其进行过滤。图8-3说明了过滤器的一般概念,其中F1是一个过滤器。它显示了请求经过滤器F1到达Servlet,Servlet产生响应再经过滤器F1到达
22、客户。这样,过滤器就可以在请求和响应到达目的地之前对它们进行监视。,8.2.1 什么是过滤器,可以在客户和资源之间建立多个过滤器,从而形成过滤器链(filter chain)。在过滤器链中每个过滤器都对请求处理,然后将请求发送给链中的下一个过滤器(如果它是链中的最后一个,将发送给实际的资源)。类似地,在响应到达客户之前,每个过滤器以相反的顺序对响应处理。图8-4说明了这个过程。这里,请求是按下列顺序 处理的:过滤器F1、过滤 器F2、过滤器F3,而响应 的处理顺序是过滤器F3、 过滤器F2、过滤器F1。,1. 过滤器是如何工作的,当容器接收到对某个资源的请求时,它首先检查是否有过滤器与该资源关
23、联。如果有过滤器与该资源关联,容器先把该请求发送给过滤器,而不是直接发送给资源。在过滤器处理完请求后,它将做下面三件事: (1)将请求发送到目标资源。 (2)如果有过滤器链,它将把请求(修改过或没有修改过)发送给下一个过滤器。 (3)直接产生响应并将其返回给客户。当请求返回到客户时,它将以相反的方向经过同一组过滤器。过滤器链中的每个过滤器都可能修改响应。,2. 过滤器的用途,Servlet规范中提到的过滤器的一些常见应用包括: 验证过滤器 登录和审计过滤器 数据压缩过滤器 加密过滤器 XSLT过滤器,8.2.2 过滤器API,表8-4描述了javax.servlet包中与过滤器有关的三个接口。
24、,1. Filter接口,Filter接口是过滤器API的核心,所有的过滤器都必须实现该接口。该接口声明了三个方法,分别是init()、doFilter()和destroy(),它们是过滤器的生命周期方法。init()是过滤器初始化方法。在过滤器的生命周期中,init()仅被调用一次。在该方法结束之前,容器并不向过滤器转发请求。该方法的声明格式为:public void init(FilterConfig filterConfig)参数FilterConfig是过滤器配置对象,通常将FilterConfig参数保存起来以备用。该方法抛出ServletException异常。,doFilter(
25、)是实现过滤的方法。如果客户请求的资源与该过滤器关联,容器将调用该方法,格式如下: public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;该方法执行过滤功能,对请求进行处理或者将请求转发到下一个组件或者直接向客户返回响应。注意,request和response参数被分别声明为ServletRequest和ServletResponse的类型。,1. Filter接口,过滤器并不只限于处理HTTP请
26、求。但如果过滤器用在使用HTTP协议的Web应用程序中,这些变量就分别指HttpServletRequest和HttpServletResponse类型的对象。在使用它们之前应把这些参数转换为相应的HTTP类型。destroy()是容器在过滤器对象上调用的最后一个方法,声明格式为: public void destroy();该方法给过滤器对象一个释放其所获得资源的机会,在结束服务之前执行一些清理工作。,1. Filter接口,2. FilterConfig接口,FilterConfig对象是过滤器配置对象,通过该对象可以获得过滤器名、过滤器运行的上下文对象以及过滤器的初始化参数。它声明了如下
27、4个方法:public String getFilterName()public ServletContext getServletContext() public String getInitParameter(String name)public Enumeration getInitParameterNames() 容器提供了FilterConfig接口的一个具体实现类,容器创建该类的一个实例、使用初始化参数值对它初始化,然后将它作为一个参数传递给过滤器的init()。,3. FilterChain接口,FilterChain接口只有一个方法,如下所示。 public void doFi
28、lter(ServletRequest request, ServletResponse response) throws IOException, ServletException在Filter对象的doFilter()中调用该方法使过滤器继续执行,它将控制转到过滤器链的下一个过滤器或实际的资源。容器提供了该接口的一个实现并将它的一个实例作为参数传递给Filter接口的doFilter()。在doFilter()内,可以使用该接口将请求传递给链中的下一个组件,它可能是另一个过滤器或实际的资源。该方法的两个参数将被链中下一个过滤器的doFilter()或Servlet的service()接收。
29、,8.2.3 一个简单的过滤器,下面是一个简单的日志过滤器,这个过滤器拦截所有的请求并将请求有关信息记录到日志文件中。程序声明的LogFilter类实现了Filter接口,覆盖了其中的init()、doFilter()和destroy()。程序8.9 LogFilter.java程序在doFilter()中首先将请求对象(request)转换成HttpServletRequest的类型,然后获得当前时间、客户请求的URI和客户地址,并将其写到日志文件中。之后将请求转发到资源,当请求返回到过滤器后再得到当前时间,计算请求资源的时间并写到日志文件中。,要使过滤器起作用必须配置过滤器。对支持Serv
30、let 3.0规范的容器,可以使用注解或DD文件的元素两种方法配置过滤器。本程序使用的是注解。下面是访问helloweb应用中的addProduct.jsp页面后,在日志中将写入下面信息。,8.2.3 一个简单的过滤器,8.2.4 WebFilter注解,WebFilter注解用于将一个类声明为过滤器,该注解在部署时被容器处理,容器根据具体的配置将相应的类部署为过滤器。表8-5给出该注解包含的常用元素。,8.2.4 WebFilter注解,上表中所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必须至少包含一个,且 value 和 urlPatter
31、ns 不能共存,如果同时指定,通常忽略 value 的取值 。过滤器接口Filter与Servlet非常相似,它们具有类似的生命周期行为,区别只是Filter的doFilter()中多了一个FilterChain的参数,通过该参数可以控制是否放行用户请求。像Servlet一样,Filter也可以具有初始化参数,这些参数可以通过WebFilter注解或部署描述文件定义。在过滤器中获得初始化参数使用FilterConfig实例的getInitParameter()。,8.2.4 WebFilter注解,在实际应用中,使用Filter可以更好实现代码复用。例如,一个系统可能包含多个Servlet,这
32、些Servlet都需要进行一些通用处理,比如权限控制、记录日志等,这将导致多个Servlet的service()中包含部分相同代码。为解决这种代码重复问题,就可以考虑把这些通用处理提取到Filter中完成,这样在Servlet中就只剩下针对特定请求相关的处理代码。下面定义一个较为实用的Filter,它对用户请求进行过滤,为请求设置编码字符集,从而可以避免为每个JSP页面、Servlet都设置字符集。该Filter还能实现验证用户是否登录,如果用户没有登录,系统直接跳转到登录页面。,8.2.4 WebFilter注解,下面定义一个较为实用的Filter,它对用户请求进行过滤,为请求设置编码字符集
33、,从而可以避免为每个JSP页面、Servlet都设置字符集。该Filter还能实现验证用户是否登录,如果用户没有登录,系统直接跳转到登录页面。程序8.10 AuthorityFilter.java,8.2.4 WebFilter注解,该过滤器通过WebFilter注解的initParams元素指定了三个初始化参数,参数使用WebInitParam注解指定,每个WebInitParam指定一个初始化参数。在Filter的doFilter()中通过FilterConfig对象取出参数的值。程序中设置了请求的字符编码,还通过Session对象验证用户是否登录,若没登录将请求直接转发到登录页面,若已登
34、录则转发到请求的资源。,8.2.4 WebFilter注解,8.2.5 在DD中配置过滤器,除了可以通过注解配置过滤器外,还可以使用部署描述文件web.xml配置过滤器类并把请求URL映射到该过滤器上。配置过滤器要用下面两个元素:和。每个元素向Web应用程序引进一个过滤器,每个元素将一个过滤器与一组请求URI关联。两个元素都是的子元素。,1. 元素,该元素用来指定过滤器名和过滤器类,下面是元素的DTD定义:从上面定义可以看到,每个过滤器都需要一个元素和一个元素。其他元素是可选的。下面代码说明了元素的使用。,validatorFilterfilter.ValidatorFilterlocaleU
35、SA,1. 元素,这里定义了一个名为validatorFilter的过滤器,同时为该过滤器定义了一个名为locale的初始化参数。这样,在应用程序启动时容器将创建一个filter.ValidatorFilter类的实例。在初始化阶段,过滤器将调用FilterConfig对象的getParameterValue(locale)检索locale参数的值。,1. 元素,2. 元素,该元素的作用定义过滤器映射,元素的DTD定义如下:元素是在元素中定义的过滤器名,用来将过滤器应用到一组通过URI标识的请求,用来将过滤器应用到通过该名标识的Servlet提供服务的所有请求。在使用情况下,模式匹配遵循与Se
36、rvlet映射同样的规则。,下面代码说明了元素的使用:validatorFilter*.jspvalidatorFilterreportServlet,2. 元素,上面的第一个映射将validatorFilter与所有的请求URL后缀为.jsp的请求相关联。第二个映射将validatorFilter与所有对名为reportServlet的Servlet的请求相关联。这里使用的Servlet名必须是部署描述文件中使用元素定义的一个Servlet。,2. 元素,3. 配置过滤器链,在某些情况下,对一个请求可能需要应用多个过滤器,这样的过滤器链可以使用多个元素配置。当容器接收到一个请求,它将查找所有
37、与请求URI匹配的过滤器映射的URL模式,这是过滤器链中的第一组过滤器。接下来,它将查找与请求URI匹配的Servlet名,这是过滤器链中的第二组过滤器。在这两组过滤器中,过滤器的顺序是它们在DD文件中的顺序。,为了理解这个过程,考虑下面对过滤器和Servlet映射代码。 FrontController *.do perfFilter FrontController ,3. 配置过滤器链,auditFilter*.do transformFilter *.do 如果一个请求URI为/admin/addCustomer.do,将以下面的顺序应用过滤器:auditFilter、transformF
38、ilter、perfFilter。,3. 配置过滤器链,4. 为转发的请求配置过滤器,从Servlet 2.4开始,过滤器还可以应用在从组件内部转发的请求上,这包括使用RequestDispatcher的include()和forward()转发的请求以及对错误处理调用的资源的请求。要为转发的请求配置过滤器,可以使用元素的子元素实现,该元素的取值包括下面4个:REQUEST、INCLUDE、FORWARD和ERROR。,REQUEST表示过滤器应用在直接来自客户的请求上。INCLUDE表示过滤器应用在与调用 RequestDispatcher的include()匹配的请求。FORWARD表示过
39、滤器应用在与调用 RequestDispatcher的forward()匹配的请求。ERROR表示过滤器应用于由在发生错误而引起转发的请求上。,4. 为转发的请求配置过滤器,在元素中可以使用多个元素使过滤器应用在多种情况下,例如:auditFilter*.doINCLUDEFORWARD上述过滤器映射将只应用在从内部转发的且其URL与*.do匹配的请求上,任何直接来自客户的请求,即使其URL与*.do匹配也将不应用auditFilter过滤器。,4. 为转发的请求配置过滤器,8.3 Servlet的多线程问题,在Web应用程序中,一个Servlet在一个时刻可能被多个用户同时访问。这时Web容
40、器将为每个用户创建一个线程。如果Servlet不涉及共享资源的问题,不必关心多线程问题。但如果Servlet需要共享资源,需要保证Servlet是线程安全的。下面首先来看一个非线程安全的Servlet。该Servlet从客户接受两个整数,然后计算它们的和或差。程序8.11 CalculatorServlet.java,该程序将计算结果存放在变量result中,它根据用户在页面中单击的是“相加”按钮或“相减”按钮决定求和还是求差。注意,result被声明为一个成员变量。为了演示多个用户请求时出现的问题,程序中调用Thread类的sleep()在计算出result后睡眠一段时间(假设2秒种),睡眠
41、的时间通过Servlet初始化参数sleepTime得到。getNumber()实现字符串到int数据的转换。最后输出计算的结果。,下面是一个JSP页面,其中的表单包含两个文本框用来接受两个整数,两个提交按钮,一个做加法、一个做减法。程序8.12 calculator.jsp 下面测试该Servlet的执行。打开两个浏览器窗口,每个窗口都载入calculator.jsp页面,在两个页面的文本框中都输入100和50,如图8-5所示。,然后单击第一个页面中的“相加”按钮,在2秒钟内单击第二个页面的“相减”按钮,得到运行结果如图8-6和图8-7所示。,从运行结果可以看到,其中一个结果是正确的(第二个
42、页面),另一个结果是错误的。该Servlet的执行过程如下:当两个用户同时访问该Servlet时,服务器创建两个线程来提供服务。当第一个用户提交表单后,它执行其所在线程的doPost(),计算100与50的和并将结果150存放在result变量中,然后在输出前睡眠2秒钟。在这个时间内,当第二个窗口提交时,它将计算100与50的差,将结果50写到result变量中,此时第一个线程的计算结果被覆盖,当第一个线程恢复执行后输出结果也为50。,出现这种错误的原因是在Servlet中使用成员变量result来保存请求计算结果,成员变量在多个线程(请求)中只有一份拷贝,而这里的result应该是请求的专有
43、数据。解决这个问题的办法是用方法的局部变量来保存请求的专有数据。这样,进入方法的每个线程都有自己的一份方法变量的拷贝,任何线程都不会修改其他线程的局部变量。,除了上述这种简单情况外,Servlet还经常要共享外部资源,如使用一个数据库连接对象。如果将连接对象声明为Servlet的成员变量,则当多个并发的请求在同一个连接上写入数据时,数据库将产生错误的数据,因此通常不用成员变量来保存请求的专有数据。,下面是编写线程安全的Servlet的一些建议: (1)用方法的局部变量保存请求中的专有数据。对方法中定义的局部变量,进入方法的每个线程都有自己的一份方法变量拷贝。如果要在不同的请求之间共享数据,应该
44、使用会话来共享这类数据。 (2)只用Servlet的成员变量来存放那些不会改变的数据。有些数据在Servlet生命周期中不发生任何变化,通常是在初始化时确定的,这些数据可以使用成员变量保存。如,数据库连接名称、其他资源的路径等。在上述例子中sleepTime的值是在初始化时设定的并在Servlet的生命期内不发生改变,所以可以把它定义为一个成员变量。,(3)对可能被请求修改的成员变量同步(使用synchronized关键字)。有时数据成员变量或者环境属性可能被请求修改。当访问这些数据时应该对它们同步,以避免多个线程同时修改这些数据。 (4)如果Servlet访问外部资源,那么需要对这些资源同步
45、。例如,假设Servlet要从文件中读写数据。当一个线程读写一个文件时,其他线程也可能正在读写这个文件。文件访问本身不是线程安全的,所以必须编写同步代码访问这些资源。,在编写线程安全的Servlet时,下面两种方法是不应该使用的: (1)在Servlet API中提供了一个SingleThreadModel接口,实现这个接口的Servlet在被多个客户请求时一个时刻只有一个线程运行。这个接口已被标记不推荐使用。 (2)对doGet()或doPost()同步。如果必须在Servlet中使用同步代码,应尽量在最小的代码块范围上进行同步。同步代码越少,Servlet执行效率越高。,8.4 Servl
46、et的异步处理,Servlet 3.0之前,Servlet的执行过程大致如下:Web容器接收到用户对某个Servlet请求之后启动一个线程,在该线程中对请求的数据进行预处理;接着,调用业务接口的某些方法,以完成业务处理;最后,根据处理的结果提交或转发响应,Servlet线程结束。其中第二步的业务处理通常是最耗时的,如访问数据库操作,跨网络调用等。此时,Servlet线程一直处于阻塞状态,直到业务执行完毕。在此过程中,线程资源一直被占用而得不到释放,对于并发用户较多的应用,这有可能造成性能的瓶颈 。,8.4.1 概 述,Servlet 3.0增加了异步处理支持,Servlet的执行过程调整如下:
47、Web容器接收到用户对某个Servlet请求之后启动一个线程,在该线程中对请求的数据进行预处理;接着,Servlet线程将请求转交给一个异步线程来执行业务处理,Servlet线程本身返回至容器,此时Servlet还没有生成响应数据。异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest和ServletResponse对象的引用),或者将请求转发给其他Servlet或JSP页面。这样,Servlet线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。,异步线程处理可应用于Servlet和过滤器两种组件,由于异步处理的工作模式和普通工作
48、模式在实现上有着本质的区别,因此默认情况下,Servlet和过滤器并没有开启异步处理特性。Servlet 3.0的异步处理是通过AsyncContext类来实现的,Servlet可以通过ServletRequest的如下两个方法创建AsyncContext对象,开启异步调用。public AsyncContext startAsync():开始异步调用并返回AsyncContext对象,其中包含最初的请求和响应对象。,public AsyncContext startAsync(ServletRequest request,ServletResponse response):开启异步调用,并传递经过包装的请求和响应对象。AsyncContext表示异步处理的上下文,该类提供了一些工具方法,可完成启动后台线程、转发请求、设置异步调用的超时时长、获取request和response对象等功能。,