
3.3 第一个Servlet
在了解了Servlet的基础知识后,现在开始编写第一个Servlet组件。
编写响应HTTP请求的Servlet只需要两步:
(1)创建一个扩展了javax.servlet.http.HttpServlet接口的类。javax.servlet.http.HttpServlet接口是javax.servlet.GenericServlet的扩展接口,它包含了分析HTTP请求Header和将客户端信息打包到javax.servlet.http.HttpServletRequest类中的相关代码。
(2)重写Servlet组件的doGet或doPost方法实现对HTTP请求信息的动态响应。这些方法是Servlet实际完成工作的地方。HTTP 1.1支持七种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TRACE。GET和POST是Web应用程序中最常用的两个方法。根据请求是通过GET还是POST发送,覆盖doGet、doPost方法之一或全部。doGet和doPost方法都有两个参数,分别为HttpServletRequest接口和HttpServletResponse接口。HttpServletRequest提供访问有关客户端请求信息的方法,包括表单数据、请求Header等。HttpServletResponse除了提供用于指定HTTP应答状态(200、404等)、应答头部信息(Content-Type、Set-Cookie等)的方法之外,最重要的是它提供了一个用于向客户端发送数据的输出流对象。这个输出流对象可以是字节流或二进制数据流。对于Servlet开发来说,它的大部分工作是操作此输出流并返回给客户端。
提示:doGet和doPost这两个方法是由service方法调用的,有时可能需要直接覆盖service方法,例如Servlet要对GET和POST两种请求采用同样的处理方式。但不推荐那样做。
Servlet也可以重写init和destroy方法以实现Servlet定制化的初始化和析构。重写init和destroy方法的典型场景是在init方法中建立数据库连接并在destroy方法中断开它。
下面开始创建Servlet。Servlet作为一个Web组件,必须包含在某个Web应用程序中,因此,首先创建Web应用程序Chapter3。
注:本章中所有的示例都包含在此Web应用程序中。
打开NetBeans开发环境,单击“文件”菜单的“新建项目”选项,弹出如图3-4所示的“新建项目”对话框。

图3-4 创建Web应用项目Chapter3
在“类别”列表框中选中Java Web选项,在“项目”列表框中选中“Web应用程序”。单击对话框底部的“下一步”按钮,进入下一页面,如图3-5所示。

图3-5 设置Web应用项目名称和位置
在“项目名称”文本框输入Chapter3。单击“项目位置”右侧的“浏览”按钮可选择项目的位置。选中“设置为主项目”复选框将当前项目设置为主项目。单击底部的“下一步”按钮,进入下一页面,如图3-6所示。

图3-6 设置Web应用服务器
Web应用程序必须发布到Java EE Web服务器上才能够运行。在这里从“服务器”下拉列表框中选择NetBeans内置的服务器“GlassFish Server 3.1.1”,默认其他选项设置,单击“完成”按钮,则Web应用程序创建完毕。
下面为Web应用创建一个Servlet。在“项目”视图中选中Web应用程序Chapter3,右击,在弹出的快捷菜单中选择“新建”→Servlet命令,弹出如图3-7所示对话框。

图3-7 “新建Servlet”对话框
在“类名”文本框中输入Servlet实现类的名称First,在“包”文本框中输入Servlet实现类所在的包名com.servlet,单击“下一步”按钮,进入下一页面,如图3-8所示。

图3-8 配置Servlet部署信息
这一步主要完成Servlet组件的部署配置,主要工作是设置Servlet的名称以及对应的URL模式名称。所谓URL模式,就是代表客户端请求的一个字符串,Web容器总是将匹配此字符串内容的请求转发到此Servlet组件来处理以便返回动态响应。“Servlet名称”文本框中的内容为Servlet的显示名称,并不要求等于前面定义的类名。在“Servlet名称”文本框中输入First。在“URL模式”文本框输入Servlet所对应的请求URL模式“/First”。选中“将信息添加到部署描述符(web.xml)”复选框,单击“完成”按钮,则一个名为First的Servlet组件创建完毕。NetBeans将在编辑器中自动打开Servlet的源代码。
在这个Servlet中,只要求Servlet在接收到请求后向客户端返回“Hello, World!”的提示信息。完整代码如程序3-1所示。
说明:为节省篇幅,代码中的一些注释信息被省略,完整的代码请到清华大学出版社的网站下载。另外,有些注释信息是NetBeans自动生成,由于机器环境的不同,读者所生成的注释信息可能与本书配套资源中的不完全一致,如create date、author信息等,这完全正常。
程序3-1:First.java
package com.servlet; import java.io.*; import java.net.*; import javax.servlet.*; import javax.servlet.http.*; public class First extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); try { out.println("<html>"); out.println("<head>"); out.println("<title>Servlet First</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Hello World! </h1>"); out.println("</body>"); out.println("</html>"); } finally { out.close(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } public String getServletInfo() { return "Short description"; } }
程序说明:NetBeans自动生成了Servlet的框架代码,其中方法doGet和doPost分别用来响应客户端发出的GET和POST请求,开发人员可以分别编写代码覆盖上述两个方法以实现对GET和POST请求的处理,在这里它们都默认调用方法processRequest(),这是NetBeans推荐的一种编程实践。方法processRequest有两个输入参数:request是代表客户端发出的请求信息的HttpServletRequest接口对象;response是代表Servlet返回客户端的响应的HttpServletResponse接口对象。在方法processRequest中,首先调用response方法setContentType("text/html; charset=UTF-8")设置响应返回类型为HTML文件,编码类型为UTF-8,然后调用response的getWriter方法获取响应对应的PrintWriter对象,最后利用PrintWriter对象的out方法在客户端打印信息“Hello World!”。
注意:方法doGet、doPost的声明中必须包含抛出两个异常(ServletException、IOException)。
为访问编写的Servlet,必须将其打包并发布到Java EE服务器上,然后启动服务器来访问Servlet。在前面创建Web应用程序的时候已经设置Java EE Web服务器为NetBeans内置的服务器GlassFish Server 5。
在“项目”视图中选中Web应用程序Chapter3,右击,在弹出的快捷菜单中选择“生成项目”命令,则Web应用被自动打包。
重新选中Web应用程序Chapter3,右击,在弹出的快捷菜单中选择“部署项目”命令,则Web应用被部署到Web服务器。再一次选中Web应用程序Chapter3,右击,在弹出的快捷菜单中选择“运行项目”命令,则Web服务器被启动且Web应用被加载运行。
说明:在执行“运行项目”的操作时,如果Web服务器已经处于运行状态,则服务器只执行加载Web应用的操作。
打开IE浏览器,在地址栏中输入http://localhost:8080/Chapter3/First,程序运行结果如图3-9所示。

图3-9 Servlet运行结果页面
读到这里可能会产生疑问:Web服务器是如何将浏览器中输入的地址http://localhost:8080/Chapter3/First自动映射到Servlet组件First的呢?
这个秘密在于:每个Web应用程序都对应一个称为上下文信息的字符串,表示此Web应用所对应的URL请求地址。在“项目”视图选中Web项目Chapter3,右击,在弹出的快捷菜单中选中“属性”命令,弹出如图3-10所示对话框。在左侧的“类别”栏目中选中“运行”,则在右侧“上下文路径”文本框中可查看并修改Web应用上下文信息。

图3-10 查看Web应用上下文路径
当Web应用程序部署到服务器上时,服务器根据此信息便知道将上下文信息为“/Chapter3”请求映射到此Web应用。
另外,为描述Web应用内部信息,每个Web应用还通常包含一个配置文件web.xml来对自身包含的Web组件的信息进行说明。Servlet添加到Web应用后,在Web应用的配置文件web.xml中包含Servlet及其URL映射信息。Java EE服务器正是根据web.xml中的配置信息将客户端的请求转发给Web应用中适当的Web组件。
web.xml的详细内容如程序3-2所示,其中的<Servlet>节点指明Servlet名称与Servlet实现类之间的对应关系。<Servlet-mapping >节点指明Servlet名称与请求URL之间的对应关系。再回头看在浏览器地址栏中输入的请求地址。地址最前面的部分“http://localhost:8080”将请求导向本机安装的Java EE服务器GlassFish Server 5。其中localhost代表本机,8080代表Java EE服务器程序的端口号。那么对于请求地址剩余信息的解析就由Java EE服务器来接管。Java EE服务器根据请求地址中的“/Chapter3”和服务器上Web应用的上下文信息确定请求由Web应用Chapter3处理响应。Java EE服务器在Chapter3 Web应用的配置文件web.xml中查找请求地址中的“/First”对应的Servlet映射信息,最终确定请求由名为First的Servlet处理响应,此Servlet对应的类文件就是刚才编写的com.servlet.First。
程序3-2:web.xml
<? xml version="1.0" encoding="UTF-8"? > <web-app version="3.0" xmlns=http://java.sun.com/xml/ns/javaee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>First</servlet-name> <servlet-class>com.servlet.First</servlet-class> </servlet> <servlet-mapping> <servlet-name>First</servlet-name> <url-pattern>/First</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> </web-app>
web.xml对于一个Web应用是如此的重要,因此在这里不得不对它多说几句。web.xml的作用就是作为Web容器与Web应用交互的场所,它一定位于应用的WEB-INF子目录下。它包含了Web应用的重要的描述信息,以本例来说,<servlet>节点用来指明Servlet逻辑名称与Java实现类之间的对应关系;<servlet-mapping>用来指明Servlet逻辑名称与URL模式之间的对应关系。当然还有其他节点用来描述Web应用其他方面的信息,详细信息可以查阅DTD(Document Type Definitions,文档类型定义)文档。Web容器正是根据web.xml文件描述的信息来操作Web应用的。
可以将程序3-2中的代码片段:
<servlet-mapping> <servlet-name>First</servlet-name> <url-pattern>/First</url-pattern> </servlet-mapping>
改为
<servlet-mapping> <servlet-name>First</servlet-name> <url-pattern>/FirstServlet</url-pattern> </servlet-mapping>
将程序3-2保存,重新发布Web应用并启动浏览器,在地址栏中输入http://localhost:8080/Chapter3/First,则将得到如图3-11所示的运行结果页面。

图3-11 修改Web应用配置后的运行结果
404错误代码表示文件无法定位的错误类型。产生错误的原因在于此时Servlet组件First对应的请求URL不再是“/First”,而是“/FirstServlet”。在地址栏中输入http://localhost:8080/Chapter3/FirstServlet,看看又会得到什么运行结果页面。
值得一提的是,自Java EE 5规范以后,推荐使用注解来代替编写复杂的配置文件。下面修改程序3-1,代码如程序3-3所示。
注:为节省篇幅,书中代码主要显示编程实践中的重点内容,完整代码请参考本书的源代码包。
程序3-3:First.java
package com.servlet; … @WebServlet(name="First", urlPatterns={"/First"}) public class First extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); try { out.println("<html>"); out.println("<head>"); out.println("<title>Servlet First</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Hello World! </h1>"); out.println("</body>"); out.println("</html>"); out.close(); } finally { out.close(); } } … }
程序说明:与程序3-1相比,在类的定义前添加了一个注解@WebServlet,它包含两个属性name和urlPatterns,分别用来定义Servlet组件的名称和URL模式。当部署此组件时,Web容器将根据此注解自动完成对此Servlet组件的配置。还要注意的是,因为使用了注解@WebServlet,因此要在代码中添加对javax.servlet.annotation.WebServlet的引用。
在“项目”视图的“配置文件”目录下删除掉web.xml,重新发布应用,打开浏览器重新输入请求地址http://localhost:8080/Chapter3/First,看看会不会得到如图3-9所示的运行结果。
说明:由于新的Java EE规范推荐采用注解的方式,因此在以后的示例中,本书将尽可能采用注解的方式来部署Web组件。但配置文件web.xml在有些情况下还是必需的,如设置Web应用的安全属性等,因此,注解并不能完全取代web.xml,它只是使得web.xml更加简洁。
如果Web组件中既采用了注解来配置组件,在配置文件web.xml中又包含了此组件配置信息,那么Web容器在进行URL解析映射时该如何进行呢?下面还是亲自动手实验一下吧。
在“项目”视图的“配置文件”目录下重新添加配置文件web.xml,内容如程序3-4所示。
程序3-4:web.xml
<? xml version="1.0" encoding="UTF-8"? > <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <servlet> <servlet-name>First</servlet-name> <servlet-class>com.servlet.First</servlet-class> </servlet> <servlet-mapping> <servlet-name>First</servlet-name> <url-pattern>/FirstServlet</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> </web-app>
程序说明:注意在程序3-4中servlet对应的URL模式(/FirstServlet)与程序3-3中注解@WebServlet对应的URL模式(/First)是不一致的。重新发布Web应用,在浏览器的地址栏输入http://localhost:8080/Chapter3/FirstServlet,将得到如图3-9所示的运行界面,而在浏览器的地址栏输入http://localhost:8080/Chapter3/First却得到一个错误提示信息。这就证明,在Web部署配置文件和注解都对Servlet进行配置的情形下,Web容器将以Web部署配置文件中的信息为准。