Servlet是个特殊的Java类,每个Servlet可以响应客户端的请求。Servlet 提供不同的方法用于响应客户端请求。
事实上,客户端的请求通常只有GET 和POST 两种, Serviet 为了响应这两种请求,必须重写doGet和doPost两个方法。如果Servlet为了响应4 种方式的请求,则需要同时重写上面的4 个方法。大部分时候, Servlet 对于所有请求的响应都是完全一样的。
可以采用继承的HttpServlet类一个方法来代替上面的几个方法:只需重写service方法即可响应客户端的所有请求。HttpServlet 还包含两个方法:
通常无须重写init和desrtoy两个方法,除非需要在初始化Servlet时,完成某些资源初始化的方法, 才考虑重写init方法。如果需要在销毁Servlet 之前,先完成某些资源的回收,比如关闭数据库连接等, 才需要重写desrtoy方法。
上面的Servlet 类继承了HttpServlet 类, 表明它可作为一个Servlet使用。程序的粗体字代码定义了service 方法来响应用户请求。
普通Servlet 类里的service方法的作用,完全等同于JSP生成Servlet 类的_jspService方法。因此原JSP 页面的JSP 脚本、静态HTML 内容, 在普通Servlet 里都应该转换成service方法的代码或输出语句:原JSP 声明中的内容,对应为在Servlet 中定义的成员变量或成员方法。
Servlet 的配置
编辑好的Servlet源文件并不能响应用户请求,还必须将其编译成class文件。将编译后的
FirstServlet.class 文件放在WEB -INF/classes 路径下,如果Servlet有包,则还应该class文件放在对应的包路径下。
为了让Serviet 能响应用户请求,还必须将Servlet 配置在Web 应用中。配置Servlet 时,需要修改web.xml 文件。
JSP/Servlet 的生命周期
JSP的本质就是Servlet , 开发者编写的JSP页面将由Web容器编译成对应的Servlet , 当Servlet 在容器中运行时,其实例的创建及销毁等都不是由程序员决定的,而是由Web 容器进行控制的。
每个Serviet 的运行都遵循如下生命周期:创建Servlet 实例。Web 容器调用Serviet 的init 方法,对Servlet 进行初始化。Servlet初始化后,将一直存在于容器中,用于响应客户端请求。如果客户端发送GET请求,容器调用Servlet 的doGet 方法处理并响应请求:如果客户端发送POST 请求,容器调用Servlet的doPost 方法处理并响应请求。或者统一使用service方法处理来响应用户请求。Web 容器决定销毁Servlet 时,先调
用Servlet 的destroy 方法,通常在关闭Web应用之时销毁Servlet 。
load-on-startup Servlet
创建Servlet实例有两个时机:用户请求之时或应用启动之时。应用启动时就创建Servlet,通常是用于某些后台服务的Servlet,或者需要拦截很多请求的Servlet:这种Servlet通
常作为应用的基础Servlet使用,提供重要的后台服务。
配置load-on-startup 的Servlet 有两种方式
<load-on-startup … ./>元素或loadOnStartup 属性都只接收一个整型值,这个整型值越小, Servlet 就越优先实例化。
访问Servlet 的配置参数
配置Servlet 时,还可以增加额外的配置参数。通过使用配置参数,可以实现提供更好的可移植性,避免将参数以硬编码方式写在程序代码中。
每个@WebinitParam 可指定如下两个属性
在web.xml 文件中为Servlet 配置参数使用< init-param. . ./ >元素,该元素可以接受如下两个子元素
第二种方式与为JSP配置初始化参数极其相似, 因为JSP的实质就是Servlet,而且配置JSP的实质就是把JSP当Servlet使用。
ServletConfig 获取配置参数的方法和ServletContext 获取配置参数的方法完全一样,只是ServletConfig 是取得当前Servlet的配置参数,而ServletContext 是获取整个Web 应用的配置参数。
Servlet 3
Servlet 3 规范在javax.servlet.annotation 包下提供了如下注解。
Servlet 3 的Web 模块支持
Servlet 3为模块化开发提供了良好的支持, Servlet 3规范不再要求所有Web 组件(如Servlet, Listener、Filter 等)都部署在web.xml 文件中,而是允许采用“Web模块”来部署、管理它们。一个Web 模块通常对应于一个JAR 包,这个JAR 包有如下文件结构。
从上面的文件结构可以看出, Web模块与普通JAR的最大区别在于需要在META-INF目录下添加一个web-fragment.xml文件,这个文件也被称为Web模块部署描述符。web-fragment.xml 文件与web.xml 文件的作用、文档结构都基本相似,因为它们都用于部署、管理各种Web 组件。只是web-fragment.xml 用于部署、管理Web 模块而己,但web-fragment.xml 文件可以多指定如下两个元素。
Web应用除了可按web -fragment.xml文件中指定的加载顺序来加载Web 模块之外,还可以通过web.xml 文件指定各Web 模块加载的绝对顺序。web.xml文件中指定的加载顺序将会覆盖Web模块中web-fragment.xml 文件所指定的加载顺序。
Servlet 3 提供的异步处理
在以前的Servlet规范中,如果Serelet作为控制器调用了一个耗时的业务方法,那么Serviet 必须等到业务方法完全返回之后才会生成响应,这将使得Serviet 对业务方法的调用变成一种阻塞式的调用,因此效率比较低。Servlet 3规范引入了异步处理来解决这个问题,异步处理允许Servlet重新发起一条新线程去调用耗时的业务方法,这样就可避免等待。
Servlet 3 的异步处理是通过AsyncContext 类来处理的, Se rvlet 可通过ServletRequest 的如下两个方法开启异步调用、创建AsyncContext 对象。
重复调用上面的方法将得到同一个AsyncContext 对象。AsyncContext 对象代表异步处理的上下文,它提供了一些工具方法,可完成设置异步调用的超时时长,dispatch 用于请求、启动后台线程、获取request 、response 对象等功能。
对于希望启用异步调用的Servlet 而言,开发者必须显式指定开启异步调用,为Servlet 开启异步调用有两种方式。
被异步请求dispatch 的目标页面需要指定sessinotallow=" false”,表明该页面不会重新创建session 。
对于支持异步调用的Servlet 来说,当Servlet 以异步方式启用新线程之后,该Servlet的执行不会被阻塞,该Servlet 将可以向客户
端浏览器生成响应一一当新线程执行完成后,新线程生成的响应再次被送往客户端浏览器。
开发者需要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这可借助于Servlet 3提供的异步监听器来实现。还需要通过AsyncContext 来注册监听器,调用该对象的addListener方法即可注册监听器。
改进的ServletAPI
Servlet 3还有一个改变是改进了部分API ,这种改进很好地简化了Java Web开发。其中两个较大的改进是:
HttpServletRequest 提供了如下两个方法来处理文件上传。
上面两个方法的返回值都涉及一个API Part , 每个Part 对象对应于一个文件上传域,该对象提供了大量方法来访问上传文件的文件类型、大小、输入流等, 并提供了一个write(String file)方法将上传文件写入服务器磁盘。
为了向服务器上传文件,需要在表单里使用<input type=”file”.../>文件域, 这个文件域会在HTML页面上产生一个单行文本框和一个“浏览”按钮,浏览者可通过该按钮选择需要上传的文件。除此之外,上传文件一定要为表单域设置enctype 属性。
如果将enctype 设置为application/x-www-form-urlenco ded ,或不设置enctype 属性,提交表单时只会发送文件域的文本框里的字符串, 也就是浏览者所选择文件的绝对路径,对服务器获取该文件在客户端上的绝对路径没有任何作用,因为服务器不可能访问客户机的文件系统。
上面的页面中的表单需要设置enctyp e=”multipart/form-data” ,这表明该表单可用于上传文件。上面的表单中定义了两个表单域: 一个普通的文本框,它将生成普通请求参数; 一个文件上传域, 它用于上传文件。
Servlet 3.1 新增的非阻塞式的IO
以Servlet 读取数据为例,传统的读取方式采用阻塞式IO-当Servlet 读取浏览器提交的数据时,如果数据暂时不可用,或数据没有读取完成, Servlet 当前所在线程将会被阻塞,无法继续向下执行。从Servlet 3.1 开始, ServletlnputStream 新增了一个setReadListener(ReadListener readListener)方法,该方法允许以非阻塞IO 读取数据,实现ReadListener 监听器需要实现如下三个方法。
类似地, ServletOuputStream 也提供了setWriterListenerer(WriteListener writeListener)方法,通过这种方式, 可以让ServletOuputStream 以非阻塞IO 进行输出。在Servlet 中使用非阻塞IO 非常简单,主要按如下步骤进行即可
上面程序调用request 的startAsync方法开启异步调用之后,程序中粗体字代码为Servlet 输入流注册了一个监听器,这样就无须在该Servlet 中使用阻塞IO 来获取数据了。而是改为由MyReadListener负责读取数据, 这样Serviet 就可以继续向下执行,不会因为IO 阻塞线程。