Servlet札记
Servlet 是一种比JSP 更早的动态网页编程技术。在没有JSP 之前, Servlet 也是同时充当视图层、业务逻辑层及持久层角色。
Servlet 的开发效率非常低,特别是当使用Servlet 生成表现层页面时,页面中所有的HTML 标签,都需采用Servlet 的输出流来输出,因此极其烦琐。由于Servlet 是个标准的Java 类,因此必须由程序员开发,其修改难度大,美工人员根本无法参与Servlet 页面的开发。这一系列的问题,都阻碍了Servlet 作为表现层的使用。
自MVC 规范出现后, Servlet 的责任开始明确下来,仅仅作为控制器使用,不再需要生成页面标签,也不再作为视图层角色使用。
Servlet ,通常称为服务器端小程序,是运行在服务器端的程序,用于处理及响应客户端请求。
Servlet 是个特殊的Java 类,这个Java 类必须继承HttpServlet 。每个Servlet 可以响应户端的请求。Servlet 提供不同的方法用于响应客户端请求。
doGet: 用于响应客户端的get 请求。
doPost: 用于响应客户端的post 请求。
doPut: 用于响应客户端的put 请求。
doDelete: 用于响应客户端的delete 请求。
事实上,客户端的请求通常只有get 和post 两种; Servlet 为了响应这两种请求,必须重写doGet 和doPost 两个方法。如果Servlet 为了响应四个方法,则需要同时重写上面的四个方法。
大部分时候, Servlet 对于所有请求的响应都是完全一样的。此时,可以采用重写一个方法来代替上面的几个方法, Servlet 只需重写service 方法即可响应客户端的所有请求。另外, HttpServlet 还包含两个方法。
init(ServletConfig config): 创建Servlet 实例时,调用的初始化方法。
destroy: 销毁Servlet 实例时,自动调用的资源回收方法。
通常无须重写init和destroy两个方法,除非需要在初始化Servlet 时,完成某些资源初始化的方法,才考虑重写init 方法。如果需要在销毁Servlet 之前,先完成某些资源的回收,比如关闭数据库连接等,才需要重写destroy 方法。
注意:如果重写了init(ServletConfig config)方法,则应在重写该方法的第一行调用super.init(config) 。该方法将调用HttpServlet 的init 方法。
Servlet 和JSP 的区别在于:
Servlet 中没有内置对象,原来JSP 中的内置对象都必须通过HttpServletRequest对象,或由HttpServletResponse 对象生成:
对于静态的HTML 标签, Servlet 都必须使用页面输出流逐行输出。
这也正是笔者在前面介绍的: JSP 是Servlet 的一种简化,使用JSP 只需要完成程序员需要输出到客户端的内容,至于JSP 中的Java 脚本如何镶嵌到一个类中,由JSP 容器完成。而Servlet 则是个完整的Java 类,这个类的service 方法用于生成对客户端的响应。
Servlet 的配置
编辑好的Servlet 源文件并不能响应用户请求,还必须将其编译成class 文件。将编译后的HelloServlet. class 文件放在WEB-INF/classes 路径下,如果Servlet 有包,则还应该将class 文件放在对应的包路径下。
为了让Servlet 能响应用户请求,还必须将Servlet 配置在Web 应用中。配置Servlet时,需要修改web.xrnl 文件。
配置Servlet 需要配置两个部分。<servlet>/<servlet-mapping>
Servlet的生命周期
Servlet 在容器中运行,其实例的创建及销毁等都不是由程序员决定的,而是由容器进行控制。
Servlet 的创建有两个选择。
客户端请求对应的Servlet 时,创建Servlet 实例:大部分的Servlet 都是这种Servlet。 Web 应用启动时,立即创建Servlet 实例:即load-on-startup Servlet。
每个Servlet 的运行都遵循如下生命周期。
(1)创建Servlet 实例。
(2) Web 容器调用Servlet 的init 方法,对Servlet 进行初始化。
(3) Servlet 初始化后,将一直存在于容器中,用于响应客户端请求。如果客户端有get 请求,容器调用Servlet 的doGet 方法处理并响应请求。对于不同的请求,有不同的处理方法,或者统一使用service 方法处理来响应用户请求。
(4) Web 容器角色销毁Servlet 时,调用Servlet 的destroy 方法,通常在关闭Web容器之时销毁Servlet。
使用Servlet创作为控制器
正如前面见到,使用Servlet 作为表现层的工作量太大,所有的HTML 标签都需要使用页面输出流生成。因此,使用Servlet 作为表现层有如下三个劣势。
开发效率低,所有的HTML 标签都需使用页面输出流完成。
不利于团队协作开发,美工人员无法参与Servlet 界面的开发。
程序可维护性差,即使修改一个按钮的标题,都必须重新编辑Java 代码,并重新编译。
整个结构非常清晰,下面是MVC 中各个角色的对应组件。
M: Model,即模型,对应JavaBean 。
V: View ,即视图,对应JSP 页面。
C: Controller,即控制器,对应Servlet。
load-on-startup Servlet
Servlet 的实例化有两个时机:用户请求之时,或应用启动之时。应用启动时就启动的Servlet 通常是用于某些后台服务的Servlet ,或者拦截很多请求的Servlet; 这种Servlet 通常作为应用的基础Servlet 使用,提供重要的后台服务。如果需要Web 应用启动时,可使用load-on-startup 元素完成Servlet 的初始化。load-on-startup 元素只接收一个整型值,这个整型值越小, Servlet 就越优先初始化。
访问Servlet 的配置参数
配置Servlet 时,还可以增加附加的配置参数。通过使用配置参数,可以实现更好地解耦,避免将所有的参数以硬编码方式写在程序中。
访问Servlet 配置参数要通过ServletConfig 类的实例完成, ServletConfig提供如下方法。
java.lang.String getInitParameter(java.lang.String name): 用于获取初始化参数。
注意: JSP 的内直对象config 就是此处的ServletConfig
自定义标签类
使用标签类,可以使用简单的标签来封装复杂的功能,从而使团队更好地协作开发(能让美工人员更好地参与JSP 页面的开发)。
自定义标签类都必须继承一个父类: java.Servlet.jsp.tagext.TagSupport 。除此之外,自定义标签类还有如下要求。
·如果标签类包含属性,每个属性都有对应的getter 和setter 方法。
·重写doStartTag或doEndTag方法,这两个方法生成页面内容。
·如果需要在销毁标签之前完成资源回收,则重写re1ease方法。
TLD 文件
TLD 是Tag Library Definition 的缩写,即标签库定义,文件的后缀是tld ,每个TLD文件对应一个标签库,一个标签库中可包含多个标签。TLD 文件也称为标签库定义文件。标签库定义文件的根元素是taglib,它可以有多个tag 子元素,每个tag 子元素都对应一个标签。
编辑了标签库定义文件还不够, Web 容器还无法加载标签库定义文件。还必须在web.xml 文件中增加标签库的定义。在web.xml 文件中定义标签库时使用taglib 元素,该元素包含两个子元素: taglib-uri和taglib-location,前者确定标签库的URI; 后者确定标签库定义文件的位置。
使用标签库
使用标签库分成以下两步。
(1) 导入标签库:使用taglib 编译指令导入标签。
(2) 使用标签:在JSP 页面中使用自定义标签。
taglib 的语法格式如下:
〈%@ taglib uri="tagliburi" prefix="tagPrefix" %〉
其中uri 属性确定标签库定义文件的URI,这个URI 就是在web.xml 文件中为标签
库定义的URI。而prefix 属性确定的是标签前缀,即在JSP 页面中使用标签时,该标签
库负责处理的标签前缀。
使用标签的语法格式如下:
<tagPrefix : tagName tagAttribute=ntagValue n ...>
<tagBody/>
</tagPrefix>
如果该标签没有标签体,则可以使用如下语法格式:
<tagPrefix : tagName tagAttribute=ntagValue n …/>
带标签体的标签
带标签体的标签,就是允许在标签内嵌套标签,通常可用于完成一些逻辑运算例如判断和循环等。
带标签体的标签需要继承BodyTagSupport,该类包含一个bodyContent 属性,该属性代表标签体。
BodyTagSupport 还包含两个方法。
doAfterBody: 每次处理完标签体后调用该方法。
void doInitBody: 开始调用标签体时调用该方法。
如果有必要,可以重写这两个方法。
在处理标签类的各个方法中,不同的返回值对应不同的含义,常用的返回值有如下几个。
SKIP_BODY: 不处理标签体,直接调用doEndTag方法。
SKIP_PAGE: 忽略标签后面的JSP 页面。
EVAL_PAGE: 处理标签结束,直接处理页面内容。
EVAL_BODY_BUFFERED: 处理标签体。
EVAL_BODY_INCLUDE: 处理标签体,但忽略setBodyContent和doInitBody方法。
EVAL_BODY_AGAIN: 对标签体循环处理。
Filter
Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链。
Filter 有如下几个用处。
·在HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest 。
·根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
·在HttpServletResponse 到达客户端之前,拦截HttpServletResponse 。
·根据需要检查HttpServletResponse ,也可以修改HttpServletResponse 头和数据。
Filter 有如下几个种类。
·用户授权的Filter: Filter 负责检查用户请求,根据请求过滤用户非法请求。
·日志Filter: 详细记录某些特殊的用户请求。
·负责解码的Filter: 包括对非标准编码的请求解码。
.能改变XML 内容的XSLTFilter 等。
一个Filter 可负责拦截多个请求或响应:一个请求或响应也可被多个请求拦截。
创建一个Filter 只需两个步骤:
(1)创建Filter 处理类:
(2) 在web.xml 文件中配置Filter。
创建Filter 类
创建Filter 必须实现javax.servle t. Filter 接口,在该接口中定义了三个方法。
void init(FilterConfig config): 用于完成Filter 的初始化。
void destroy: 用于Filter 销毁前,完成某些资源的回收。
void doFilter(ServletRequest request, ServletResponse response,FilterChain chain): 实现过滤功能,该方法就是对每个请求及响应增加的额外处理。
执行chain.doFilter(request,reponse)方法,当Filter 对请求过滤后,依然将请求发送到目的地址。如果检查权限,可以在Filter 中根据用户请求的HttpSession,判断用户权限是否足够。
如果权限不够,则调用重定向即可,无须调用chain.doFilter(request,reponse)方法。
配置Filter
Filter 的配置和Servlet 的配置非常相似,都需要配置两个部分:
·配置Filter 名。
·配置Filter 拦截URL 模式。
区别在于, Servlet 通常只配置一个URL ,而Filter 可以同时拦截多个请求的URL。
因此,可以配置多个Filter 拦截模式。
Listener
Listener 的作用非常类似于load-on-startup Servlet。用于在Web 应用启动时,启动某些后台程序,这些后台程序负责为系统运行提供支持。
Listener 与load-on-startup Servlet 的区别在于: Listener 的启动时机比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 规范之后才出现的。
使用Listener 只需要两个步骤:
(1) 创建Listener 实现类。
(2) 在web.xml 文件中配置Listener。
创建Listener 类
创建Li stener 类必须实现ServletContex tListener 接口,该接口包含两个方法。
eontextInitialized(ServletContextEvent see): 启动Web 应用时,系统调用该Filter的方法。
eontextDestroyed(ServletContextEvent see): 关闭Web 应用时候,系统调用Filter
的方法。
配置Listener
正如load-an-startup Servlet 一样, Listener 用于启动Web 应用的后台服务程序,但不负责处理及响应用户请求,因此无须配置URL。
若将Listener 配置在Web 容器中(如果Web 容器支持Listener),则Listener 将随Web 应用的启动而启动。
配置Listener 时使用<listener/>元素,下面是配置Listener 的片段:
<!-- 配置Listener-->
<listener>
<!- 指定Listener 的实现类→
<listener-class>lee.ScheduleListener</listener-class>
</listener>
在上面的配置中,既无须配置Listener 的名字,也无须配置Listener 的URL 只需配置它的实现类即可。此时容器将自动检测部署在容器中的Listener,并在应用启动时,自动加载所有的Listener。