第5章Servlet技术深入剖析 Servlet 是一个技术体系和规范,它提供了一组类、接口和 协议,用于满足Web 服务器所需功能。了解这些类和接口,有 助于对Servlet 技术的全面认识。在本章中,重点介绍过滤器 及监听器技术,并通过实例让读者对这些常用类和接口的实际 应用有全面了解。 5.1 Servlet技术体系 前面介绍了Servlet技术的基础知识。为了全面了解Servlet技术体系,本节介绍 Servlet的常用类和接口的用法。Servlet的常用类和接口如图5-1所示。 图5-1 Servlet 的常用类和接口 5.1.1 常用类和接口 1.与Servlet实现相关的类和接口 publicinterfaceServlet是所有Servlet必须直接或间接实现的接口。它定义了以下 方法: .init(ServletConfigconfig)方法:用于初始化Servlet。 .destory()方法:用于销毁Servlet。 .getServletInfo()方法:用于获得Servlet的信息。 .getServletConfig()方法:用于获得Servlet的配置信息。 .service(ServletRequestreq,ServletResponseres)方法:是应用程序运行的逻辑入 口点。 publicabstractclasGenericServlet类提供了Servlet接口的基本实现。它是一个抽象 类。GenericServlet类的service()方法是一个抽象的方法,GenericServlet类的派生类必须 直接或间接实现这个方法。 publicabstractclasHtpServlet类是针对使用HTTP的Web服务器的Servlet类。 HtpServlet类实现了抽象类GenericServlet的service()方法,该方法的功能是根据请 求类型调用合适的do方法。do方法是由用户定义的Servlet根据特定的请求/响应情况具 体实现的。也就是说,必须实现以下方法之一: .doGet()。如果Servlet支持HTTPGET请求,则要实现该方法。 .doPost()。如果Servlet支持HTTPPOST请求,则要实现该方法。 .其他do方法。用于HTTP其他方式的请求。 2.与请求、响应会话跟踪和Servlet上下文相关的接口 publicinterfaceHtpServletRequest接口中最常用的方法就是获得请求中的参数。实 际上,内置对象request就是实现该接口的类的一个实例,关于该接口的方法和功能在前面 已讲述了,这里不再重复。 publicinterfaceHtpServletResponse接口代表了对客户端的HTTP响应。实际上,内 置对象response就是实现该接口的类的一个实例,关于该接口的方法和功能在前面已讲述 了,这里不再重复。 会话跟踪接口(HtpSesion)、Servlet上下文接口(ServletContext)与HtpServletRequest接 口类似,不再重复介绍。但有一点需要注意,eve但二者获取内置 JSP与Srlt的内置对象相似, 对象的方法略有不同JSP与Servlet的比较如表5-1所示。 89 90 表5-1 JSP与Servlet的比较 技术请求对象响应对象会话跟踪上下文内容对象 JSP request由容器产 生,直接使用 response 由容器 产生,直接使用 session由容器产生,直 接使用 application由容器产生, 直接使用 Servlet request由容器产 生,直接使用 response 由容器 产生,直接使用 HttpSessionsession= request.getSession() 用getServletContext() 方法获取 3.RequestDispatcher接口 RequestDispatcher接口代表Servlet协作,在前面已用到。它可以把一个请求转发到另 一个Servlet或JSP页面。该接口主要有两个方法: .forward(ServletRequest,ServletResponseresponse)方法。用于把请求转发到服务 器上的另一个资源。 .include(ServletRequest,ServletResponseresponse)方法。用于把服务器上的另一个 资源包含到响应中。 RequestDispatcher接口的forward()方法处理请求转发,在Servlet中是一个很有用的 功能。由于这种请求转发属于request对象的范围,所以,应用程序往往用这种方法从 Servlet向JSP页面或另一Servlet传输程序数据。其核心代码格式如下: request.setAttribute("key", 任意对象数据); RequestDispatcher dispatcher=null; dispatcher= getServletContext ( ). getRequestDispatcher ( " 目的JSP 页面或另一个 Servlet"); dispatcher.forward(request, response); 在以上代码中,RequestDispatcher的实例化由上下文的.getRequestDispatcher()方法 实现,在目的JSP页面或另一个Servlet中,用户程序可以用request.setAttribute("key")方 法获取传递的数据。另外,需要注意的是,利用RequestDispatcher接口的forward()方法处 理请求转发,其作用类似于JSP中的动作标签,属于服务器内部跳转。实际 上,JSP中的动作标签的底层实现就利用了RequestDispatcher技术。 4.Filter、FilterChain、FilterConfig等过滤器接口 Filter、FilterChain、FilterConfig等过滤器接口在Web应用中是比较有用的技术。例 如,通过过滤器接口,可以完成统一编码(中文处理技术)、安全认证等工作。 5.1.2 全面了解Servlet 配置 配置Servlet的目的除了前面所述的以外,还可以通过 等元素的设置使容器(Tomcat)能够根据配置规则管理Servlet,从而实现一些特定 的目标。Servlet配置工作可在web.xml中完成。 91 1.元素及其子元素 在web.xml文件中,要注意各元素的顺序。元素放在 元素之前,否则会导致web.xml移植困难。 以下为元素及其子元素: 描述内容 显示名 Servlet 名 Servlet 类名 JSP 文件名 参数名 参数值 一个整型值 角色名 角色的一个引用 以上元素中,元素可以有0个 或多个,元素可以有0个或1个。 各元素的作用及含义说明如下: 元素为Servlet指定一个文本描述,无多大实际意义和作用。 元素为Servlet指定一个简短的名字,这个名字可以被某些工具显示, 无多大实际意义和作用。 元素指定Servlet名,其作用相当于Servlet类的实例名,在程序中必 须是唯一的。 元素指定Servlet类名,且是完整限定名称,即包括包名(路径)。 元素指定一个JSP文件名,且是完整限定名称(包括相对或绝对路径)。 元素用来定义初始化参数,可以有多个元素。在Servlet 的类中通过getInitParamenter(Stringname)方法访问初始化参数,这类似于JSP中的动作 元素标签元素指定当Web应用启动时装载Servlet的次序。当值为正数或 零时,Servlet容器先加载数值小的Servlet,再依次加载其他数值大的Servlet;当值为负或未 定义时,Servlet容器将延迟装载时间,也就是在Web客户端首次访问某个Servlet时才加 载它。元素用于声明在组件或部署组件的代码中安全角色的引用。 92 2.元素及其子元素 元素相对简单,只有两个子元素,如下所示: Servlet 名 URL 路径 其中,元素与前述一致。元素在前面也已使用过,但该 元素还有其他的作用。它主要通过通配符配置URL映射,对多个匹配的URL进行响应,而 JSP页面只能通过一个具体的URL调用。这个特性可以使用户程序在请求进入某个具体 的页面前被截获和处理。许多Web应用框架(如Struts、Spring)都利用了Servlet的这个特 性,并在此基础上创建构架。 3.利用注解替换配置文件 其实,以上在web.xml中的所有配置内容都可以利用@WebServlet注解的相关属性实 现相同的功能。 5.2 过滤器技术 5.2.1 基本概念 Servlet过滤器在JavaServlet2.3规范中定义。 过滤器是小型的Web组件,若服务器(如Tomcat)中部署了过滤器,则对于从客户端发 送过来的请求,服务器首先让过滤器执行,这可能是安全、权限检查,也可能是字符集统一过 滤处理;然后,或者让客户请求的目的页面或Servlet处理,或者直接进行页面转发(假如安 全检查没有通过)。如果系统中设置了多个过滤器(一般情况下,一个过滤器完成一项特定 任务),则一组过滤器会形成一个过滤链,客户请求会在过滤链中逐步过滤执行。 Java中的过滤器并不是一个标准的Servlet,它不能处理用户请求,也不能对客户端生 成响应。它主要用于对HttpServletRequest进行预处理,也可以对HttpServletResponse进 行后处理,是一个典型的处理链。 过滤器有如下几个用处: . 在HttpServletRequest到达Servlet之前对其进行拦截。 . 根据需要检查HttpServletRequest,可修改HttpServletRequest头和数据。 . 在HttpServletResponse到达客户端之前对其进行拦截。 . 根据需要检查HttpServletResponse,可修改HttpServletResponse头和数据。 93 过滤器有如下几类: . 用户授权的过滤器。负责检查用户请求,根据请求过滤用户的非法请求。 . 日志过滤器。详细记录某些特殊的用户请求。 . 负责解码的过滤器。包括对非标准编码的请求解码。 5.2.2 过滤器的主要方法、生命周期与部署 所有实现了javax.servlet.Filter接口的类都被称为过滤器类。这个接口含有3个过滤 器类必须实现的方法: (1)init(FileterConfigfileterconfig):由Servlet容器调用,是Servlet过滤器的初始化 方法,Servlet容器创建Servlet过滤器实例后将立即调用这个方法,且该方法只被调用一次。 在这个方法中可以读取web.xml文件中的Servelt过滤器的初始化参数。 (2)doFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain):这 个方法完成实际的过滤操作。当客户请求访问与过滤器相关联的URL时,Servlet容器将 首先调用过滤器的doFilter()方法。FilterChain参数用于访问后续过滤器,该参数中的 doFilter(Servletrequestrequest,Servletresponseresponse)方法真正决定了是否要继续访问 后续的过滤器。 (3)destroy():Servlet容器在销毁过滤器实例前调用这个方法,在这个方法中可以释 放Servlet过滤器占用的资源。 过滤器的生命周期如下: (1)启动服务器时加载过滤器的实例,并调用init()方法初始化实例。 (2)每一次请求时都只调用doFilter()方法进行处理。 (3)停止服务器时调用destroy()方法销毁实例。 过滤器编写完成之后,需要配置与部署。这个工作有两种方法。 1.通过web.xml进行配置与部署 通过web.xml进行配置与部署是一种传统的做法,主要涉及两个元素:元素格式如下: 过滤器名称 过滤器对应的类 参数名称1 参数值1 94 参数名称2 参数值2 元素的作用就是确定过滤器与特定URL的关联。只有指定Servlet 过滤器和特定的URL关联,当客户请求访问此URL时,才会触发过滤器工作。过滤器的关 联方式有3种:与一个URL关联,与一个URL目录下的所有资源关联,与一个Servlet关 联。元素格式如下: (1)与一个URL关联时: 过滤器名称 xxx.jsp(或者xxx.html) (2)与一个URL目录下的所有资源关联时: 过滤器名称 /* (3)与一个Servlet关联时: 过滤器名称 Servlet 名称 2.通过注解进行配置与部署 从Servlet3.0标准开始,过滤器也提供了注解方法,与Servlet的注解类似。例如: @WebFilter(filterName="SecurityCheck",value="/securitycheck/*") public class SecurityCheck implements Filter {…} 在实际开发中,常用到的过滤器有身份验证过滤器(authenticationfilter)、字符集转换 过滤器(encodingfilter)、加密过滤器(encryptionfilter)和图像转换过滤器(image conversionfilter)等。 5.2.3 过滤链 doFilter()方法的参数chain是接口FilterChain的实例,由服务器生成。若项目中有多 95 个过滤器,EncodingFilter负责设置编码,SecurityFilter负责控制权限,服务器会按照项目 中过滤器定义的先后顺序将过滤器组装成一条链,然后依次执行其中的doFilter()方法。假 设过滤器的定义顺序是EncodingFilter在前、SecurityFilter在后,则其执行的顺序如下: (1)执行第一个过滤器的doFilter()方法中的chain.doFilter()之前的代码,例如进行统 一编码: request.setCharacterEncoding(newCharSet); (2)执行第二个过滤器的doFilter()方法中的chain.doFilter()之前的代码。 (3)执行请求的资源(如Servlet、JSP等)。 (4)执行第二个过滤器的chain.doFilter()之后的代码。 (5)执行第一个过滤器的chain.doFilter()之后的代码,最后将响应返回客户端。 以上执行顺序可用图5-2表示。 图5-2 过滤器执行顺序 5.2.4 安全过滤器的开发 为了防止用户绕过登录(直接输入具体页面的URL)或者登录失效时对页面进行非法 操作,应对系统的所有请求进行安全过滤操作。前面章节的基本思路是对每个页面分别进 行安全检查,这样显然有代码冗余。现在采用集中过滤检查方法,基本思路是把需要过滤的 服务器资源集中放在一个目录中,如把个人中心、支付中心等页面放在securitycheck/目录 中,项目结构如图5-3所示。 当用户访问该目录下的资源时,则受到安全检查。过滤器核心代码如下: package filter; import javax.servlet.Filter; 96 图5-3 项目结构 import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @WebFilter(filterName="SecurityCheck",value="/securitycheck/*") public class SecurityCheck implements Filter { … public void doFilter ( ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request=(HttpServletRequest)req; HttpServletResponse response=(HttpServletResponse)res; HttpSession session=request.getSession(); String username=(String)session.getAttribute("name"); //条件成立时,不需要过滤,进入下一个过滤器或转到Servlet if(username!=null) { //System.out.println("filter suc"); chain.doFilter(req,res); } else response.sendRedirect("/Filter/bookmain.html"); … } } 97 代码说明: HttpServletRequest和ServletRequest都是接口,前者继承自后者,HttpServletRequest比 ServletRequest多了一些针对HTTP 的方法,如getHeader(Stringname)、getMethod()、 getSession()、getRequestURI()等。如果业务需要用到HttpServletRequest的方法,则必须进行 类型转换,代码如下: HttpServletRequest request=(HttpServletRequest)req; req是ServletRequest类型的对象,这类似于以下情形: session.setAttribute("name", "张三"); String name=(String)session.getAttibute("name"); 过滤器配置采用注解方式: @WebFilter(filterName="SecurityCheck",value="/securitycheck/*") 注解中的filterName="SecurityCheck"相当于配置文件中的元素: SecurityCheck filter.SecurityCheck 注解中的value="/securitycheck/*"相当于配置文件中的元素: SecurityCheck /securitycheck/* 5.3 监听器技术 5.3.1 基础知识 监听器(listener)用于监听JavaWeb程序中的各类事件,它是Servlet规范中的特殊类,也 需要在web.xml文件中注册和配置才可以使用(除了两个例外)。它监听ServletContext、 HttpSession和ServletRequest等对象的创建与销毁事件以及这些对象中属性发生改变的事件。 当这些事件发生的时候,对应的监听器会立刻做出反应。目前一共有8个监听器接口和6个 事件类别,如表5-2所示。