浅谈Servlet(一)
在正式谈Servlet之前,首先看看JavaEE API规范对Servlet的定义:Servlet是一个运行在Web服务器中的Java小程序。
Servlet将会接收和响应来自Web客户端的请求,使用HTTP(超文本传输协议)进行通讯。
(一)初始Servlet
Servlet是所有Web应用程序的核心类,它是唯一的既可以直接处理和响应用户请求,也可以将处理工作委托给应用中的其他部分的类。除非某些过滤器提前终止了客户端的请求,否则所有的请求都将被发送到某些Servlet中。运行应用程序的Web容器将会有一个或多个內建的Servlet。这些Servlet将用于处理各种用户请求,诸如JSP页面、逻辑运算、访问资源等;
所有的Servlet都实现了javax.servlet.Servlet接口,但通常不是直接实现的,它只是一个简单的接口,包含了初始化并销毁Servlet和处理请求的方法,简化代码如下:
public interface Servlet {
// 初始化Servlet
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
// 处理和响应用户请求
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
// 销毁Servlet
public void destroy();
}
不论什么类型的请求,甚至是非HTTP请求(理论上,假设使用的Web容器支持这样的请求)都将会调用service方法。将来的某一天JavaEE有可能会添加新的Servlet用户处理文件传输协议(FTP)。出于这个原因,JavaEE中有许多可用于继承Servlet类。需要注意的是目前Java EE7支持的唯一Servlet协议就是HTTP,这也是你发现最底层实现Servlet类只有HTTPServlet的原因;
在大多数情况下,Servlet都继承自GenericServlet。GenericServlet仍然是一个不依赖于具体协议的Servlet,它只包含了一个抽象的service方法,同时还包含了几个辅助方法用于日志操作和从应用和Servlet配置中获取信息(具体参见Generic源码);
由于GenericServlet不依赖于任何协议,所以要处理HTTP协议的请求,就需要使用HttpServlet类,该类继承自GenericServlet,然后提供了响应每种HTTP方法类型的方法的空实现,如下:
由于目前为止Servlet只支持HTTP协议,所以我们将总是继承HttpServlet,下面我们创建如下一个Servlet,
package com.yxd.servlet;
import javax.servlet.http.HttpServlet;
public class HelloServlet extends HttpServlet{}
通过上述方式,该Servlet可以接受任何HTTP请求,但却返回一个405 Method Not Allowed错误。这是为什么呢?查看HttpServlet源码,就会发现,原来HttpServlet在提供针对不同的请求方法实现的时候,都添加了如下类似的实现,这就要求我们必须重写对应的请求方法,比如用get方式提交请求的,重写doGet(),其余依次类推
那好,接下来我们提供一个简单的doGet()方法实现,如下:
package com.yxd.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().println("Hello, World!");
}
}
现在该Servlet可以对GET请求作出响应,并在响应主体中显示出普通文本“Hello, World!”;有IO流知识的同学会发现这不就是一个通过响应对象获取一个字符打印输出流,然后通过这个流返回响应内容。可以看出通过Servlet后不再担心任何原生HTTP请求或响应的细节。那到底是谁干了这个工作呢?没错,Web容器会处理请求的解释,并从套接字(socket)中读取请求头和参数。在Servlet的方法返回之后,Web容器还将格式化响应头和主体,并协会到套接字中;
注意:学过IO流的会发现,获取了一个PrintWriter对象,最后为什么没有调用对应的close方法呢?一般来说,在Java中只需要关闭自己创建的资源即可。Web容器创建了该资源,所以它也会负责关闭该资源;
在完成并运行第一个Servlet时,可能会注意到init和destroy方法。那这两个方法有什么作用呢?当Web容器启动Servlet时,将会调用Servlet的init方法。在Web容器关闭Servlet时,将会调用Servlet的destory方法;
(二)Servlet的初始化方法和销毁方法
1、首先说说初始化方法,JavaEE API在Servlet接口中声明了如下方法:
同时在GenericServlet中提供了对Servlet接口中init方法的实现,但是又额外提供了一个不带参数的init方法的空实现,如
图,所以尽管可以重写原始方法,但是更推荐重写上述空实现的方法,因为如果之后忘记了调用父类中的方法,那么该Servlet可能无法正确实例化;
init方法可以完成许多操作。更为重要的是,init方法在Servlet构造完成之后自动得到调用,但在响应第一个请求之前。与构造器不同,在调用init方法时,Servlet中的所有属性都已经设置完毕,并提供了对ServletConfig和javax.servlet.ServletContext对象的访问。所以使用该方法可以读取属性文件,或者JDBC连接数据库。该方法的调用时机如下:
(1)如果无特殊配置,则将在第一次请求访问它接收的Servlet时调用;
(2)如果将Servlet配置为在Web应用程序部署和启动时自动启动,那么将随Web容器一同调用;
2、再来说说destroy(),先来看看它在Servlet的声明:同样,在GenericServlet中提供了空实现,如下:
destory在Servlet不再接收请求之后立即调用。一般来说发生在Web的应用程序被停止或卸载,或者Web容器关闭时。它的调用时机是在卸载或关闭应用时立即调用,所以不需要等待垃圾回收在清理资源之前触发终止化器。因此应该使用destrory方法清理Servlet持有的资源(在所有请求的处理过程中);
(三)配置可部署的Servlet
在上述一、二中,我们了解且可以创建一个可以响应HTTP请求并发出问候的类,但是我们尚未编写指令告诉容器如何部署应用程序中的Servlet。由于一个Web项目的大部分配置项目都需要在部署描述符(web.xml)中部署,下面开始部署Servlet,分两部分进行:
1、向部署描述符中添加Servlet
2、将Servlet映射到URL
示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 下列web-app的XML Schema URI都是为了兼容JavaEE7而必须使用的URI -->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 设置应用程序在应用服务器中显示的名字 -->
<display-name>Hello World Application</display-name>
<!-- 向部署描述符中添加Servlet -->
<servlet>
<!-- 指定Servlet名字,一般是Servlet类首字母小写后的字符串 -->
<servlet-name>helloServlet</servlet-name>
<!-- Servlet类的完全限定名 -->
<servlet-class>com.yxd.HelloServlet</servlet-class>
<!-- 下面这个配置的作用表示Web容器在应用程序启动后立即启动Servlet,即调用init方法
多用在比如某个Servlet的init方法需要完成许多工作,花费很长时间的情况 -->
<!-- 比如有多个Servlet都配置了该标签,按照值大小启动,即数越小启动越快;数一样时,按照在部署符中的顺序启动 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 将Servlet映射到URL -->
<servlet-mapping>
<!-- 需要与Servlet标签中的servlet-name属性值一致 -->
<servlet-name>helloServlet</servlet-name>
<!-- 所有访问应用程序相对URL/greeting的请求都将由helloServlet处理 -->
<url-pattern>/greeting</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
需要说明的有两点:
1、<servlet>和<servlet-mapping>标签中的子标签<servlet-name>属性值应该一致。Web容器通过这种方式关联这两个配置。如果应用程序在部署后的URL为:http://localhost:8080/helloweb,那么Servlet相应的URL地址为http://localhost:8080/helloweb/greeting
2、多个url-pattern可以映射到同一个Servlet,具体根据实际逻辑而定!