本文摘自:李刚 《Java EE企业级应用实战——Struts2+Spring+Hibernate》
Servlet是一种比JSP 更早的动态网页编程技术。在没有JSP之前, Servlet也是同时充当视图层、业务逻辑层及持久层角色。Servlet 的开发效率非常低,特别是当使用Servlet 生成表现层页面时,页面中所有的HTML 标签,都需采用Servlet 的输出流来输出,因此极其烦琐。由于Servlet是个标准的Java 类,因此必须由程序员开发,其修改难度大,美工人员根本无法参与Servlet 页面的开发。这一系列的问题,都阻碍了Servlet 作为表现层的使用。自MVC 规范出现后, Servlet 的责任开始明确下来,仅仅作为控制器使用,不再需要生成页面标签,也不再作为视图层角色使用。
一. Servlet开发
Servlet ,通常称为服务器端小程序,是运行在服务器端的程序,用于处理及响应客户端的请求。
Servlet 是个特殊的Java 类,这个Java 类必须继承HttpServlet 。每个Servlet 可以响应客户端的请求。Servlet 提供不同的方法用于响应客户端请求。
(1)doGet:用于响应客户端的get 请求。
(2)doPost:用于响应客户端的post 请求。
(3)doPut:用于响应客户端的put 请求。
(4)doDelete:用于响应客户端的delete 请求。
事实上,客户端的请求通常只有get 和post 两种; Servlet 为了响应这两种请求,必须重写doGet 和doPost 两个方法。如果Servlet 为了响应四个方法,则需要同时重写上面的四个方法。
大部分时候, Servlet 对于所有请求的响应都是完全一样的。此时,可以采用重写一个方法来代替上面的几个方法, Servlet 只需重写service 方法即可响应客户端的所有请求。另外, HttpServlet 还包含两个方法。
(1)init(ServletConfig config):创建Servlet 实例时,调用的初始化方法。
(2)destroy():销毁Servlet 实例时,自动调用的资源回收方法。
通常无须重写init()和destroy()两个方法,除非需要在初始化Servlet 时,完成某些资源初始化的方法,才考虑重写init 方法。如果需要在销毁Servlet 之前,先完成某些资源的回收,比如关闭数据库连接等,才需要重写destroy 方法。
注意:如果重写了init(ServletConfig config)方法,则应在重写该方法的第一行调用super.init(config) 。该方法将调用HttpServlet的init 方法。
下面提供一个Servlet 的示例,该Servlet 将获取表单请求参数,并将请求参数显示给客户端:
package ppp;
import java.io.PrintStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//Servlet必须继承HttpServlet 类
public class FirstServlet extends HttpServlet {
// 客户端的响应方法,使用该方法可以响应客户端所有类型的请求
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, java.io.IOException {
// 设置解码方式
request.setCharacterEncoding("GBK");
// 获取name 的请求参数值
String name = request.getParameter("name");
// 获取gender 的请求参数值
String gender = request.getParameter("gender");
// 获取color 的请求参数值
String[] color = request.getParameterValues("color");
// 获取country 的请求参数值
String national = request.getParameter("country");
// 获取页面输出流
PrintStream out = new PrintStream(response.getOutputStream());
// 输出HTML 页面标签
out.println("<!DOCTYPE HTML PUBLIC\"-//W3C//DTD HTML 4.0 Transitional//EN\">");
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Servlet测试</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
// 输出请求参数的值: name
out.println("您的名字: n" + name + " <hr> ");
// 输出请求参数的值: gender
out.println("您的性别: " + gender + "<hr>");
// 输出请求参数的值: color
out.println("您喜欢的颜色: ");
for (String c : color)
out.println(c + " ");
out.println("chr>");
out.println("您喜欢的颜色:");
// 输出请求参数的值: national
out.println("您来自的国家: " + national + "<hr>");
out.println("c/BODY>");
out.println("c/HTML>");
}
}
Servlet 和JSP 的区别在于:Servlet 中没有内置对象,原来JSP中的内置对象都必须通过HttpServletRequest对象,或由HttpServletResponse 对象生成;对于静态的HTML 标签, Servlet 都必须使用页面输出流逐行输出。
二. Servlet的配置
编辑好的Servlet源文件并不能响应用户请求,还必须将其编译成class 文件。将编译后的FirstServlet. class 文件放在WEB-INF/classes 路径下,如果Servlet有包,则还应该将class 文件放在对应的包路径下。
为了让Servlet 能响应用户请求,还必须将Servlet 配置在Web 应用中。配置Servlet时,需要修改web.xrnl 文件。
配置Servlet 需要配置两个部分。
(1)配置Servlet 的名字:对应web.xml 文件中的<Servlet!>元素。
(2)配置Servlet 的URL:对应web.xrnl 文件中的<servlet-mapping/>元素。
因此,配置一个能响应客户请求的Servlet ,至少需要配置两个元素,关于上面FirstServlet 的配置如下:
<!--配置Servlet 的名字>
<servlet>
<!--指定Servlet 的名字-->
<servlet-name>firstServlet</servlet-name>
<!-- 指定Servlet 的实现类-->
<servlet-class>ppp.firstServlet</servlet-class>
</servlet>
<!--配置Servlet的URL-->
<servlet-mapping>
<!--指定Servlet的名字-->
<servlet-name>firstservlet</servlet-name>
<!--指定Servlet映射的URL地址-->
<url-pattern>/firstServlet</url-pattern>
</servlet-mapping>
三. Servlet 的生命周期
Servlet 在容器中运行,其实例的创建及销毁等都不是由程序员决定的,而是由容器进行控制。Servlet 的创建有两个选择:
(1)客户端请求对应的Servlet 时,创建Servlet 实例:大部分的Servlet 都是这种Servlet 。
(2)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创作为控制器
在标准的MVC模式中,Servlet仅作为控制器使用。下面介绍一个使用Servlet 作为控制器的MVC 应用,该应用演示了一个简单的登录验证:
首先是一个标准的登录页面,该页面将收集的用户名及密码,提交到Servlet。
<%@ page language="java" contentType="text/html;charset=gb23l2" errorPage=
"error.jsp"%>
<html>
<head>
<title>登录</title>
</head>
<body>
<!--输出出错提示-->
<font color="red">
<%
if(request.getAttribute("err")!= null)
{
out.println(request.getAttribute("err"));
}
%>
</font>
请输入用户名和密码:
<!--登录表单,该表单提交到一个Servlet-->
<form id="login" method="post" action="login">
用户名: <input type="text" name="username"/><br>
密 码: <input type="password" name="pass"/><br>
<input type="submit" value="登录"/><br>
</form>
</body>
</html>
在这里,Servlet充当控制器角色,控制器Servlet 的源代码如下:
package ppp;
import java.sql.ResultSet;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginServlet extends HttpServlet {
// 响应客户端请求
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, java.io.IOException {
// Servlet本身并不输出响应到客户端,因此必须将请求转发
RequestDispatcher rd;
// 获取请求参数
String username = request.getParameter("username");
String pass = request.getParameter("pass");
String errMsg = request.getParameter("errMsg");
try {
// Servlet 本身,并不执行任何的业务逻辑处理,它调用JavaBean处理用户请求
DbDao dd = DbDao.instance("com.mysql. jdbc. Driver",
"jdbc:mysql:lllocalhost:3306/liuyan", "root", "32147");
// 查询结果集
ResultSet rs = dd.query("select password from user_table where username ='"
+ username + "'");
if (rs.next()) {
// 用户名和密码匹配
if (rs.getString("password").equals(pass)) {
// 获取session 对象
HttpSession session = request.getSession(true);
// 设置session属性,跟踪用户会话状态
session.setAttribute("name", username);
// 获取转发对象
rd = request.getRequestDispatcher("/welcome.jsp");
// 转发请求
rd.forward(request, response);
} else {
// 用户名和密码不匹配时
errMsg += "您的用户名密码不符合,请重新输入";
}
} else {
// 用户名不存在时
errMsg += "您的用户名不存在,请先注册";
}
} catch (Exception e) {
rd = request.getRequestDispatcher("/error.jsp");
request.setAttribute("exception", "业务异常");
rd.forward(request, response);
}
// 如果出错,转发到重新登录
if (errMsg != null && !errMsg.equals("")) {
rd = request.getRequestDispatcher("/login.jsp");
request.setAttribute("err", errMsg);
rd.forward(request, response);
}
}
}
控制器负责接收客户端的请求,它既不直接对客户端输出晌应,也不处理用户请求,只将请求转发到JSP 页,通过调用JavaBean 来处理用户请求。
下面是Model JavaBean 的源代码:
package ppp;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class DbDao {
// 该JavaBean 做成单态模式
private static DbDao op;
private Connection conn;
private String driver;
private String url;
private String username;
private String pass;
// 构造器私有
private DbDao() {
}
// 构造器私有
private DbDao(String driver, String url, String username, String pass)
throws Exception {
this.driver = driver;
this.url = url;
this.username = username;
this.pass = pass;
Class.forName(driver);
conn = DriverManager.getConnection(url, username, pass);
}
// 下面是各个成员属性的setter 和getter 方法
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getDriver() {
return (this.driver);
}
public String getUrl() {
return (this.url);
}
public String getUsername() {
return (this.username);
}
public String getPass() {
return (this.pass);
}
// 获取数据库连接
public void getConnection() throws Exception {
if (conn == null)
Class.forName(this.driver);
conn = DriverManager.getConnection(this.url, this.username, this.pass);
}
// 实例化JavaBean 的入口
public static DbDao instance() {
if (op == null)
op = new DbDao();
return op;
}
// 实例化JavaBean 的入口
public static DbDao instance(String driver, String url, String username,
String pass) throws Exception {
if (op == null) {
op = new DbDao(driver, url, username, pass);
}
return op;
}
// 插入记录
public boolean insert(String sql) throws Exception {
getConnection();
Statement stmt = this.conn.createStatement();
if (stmt.executeUpdate(sql) != 1) {
return false;
}
return true;
}
// 执行查询
public ResultSet query(String sql) throws Exception {
getConnection();
Statement stmt = this.conn.createStatement();
return stmt.executeQuery(sql);
}
// 执行删除
public void delete(String sql) throws Exception {
getConnection();
Statement stmt = this.conn.createStatement();
stmt.executeUpdate(sql);
}
// 执行更新
public void update(String sql) throws Exception {
getConnection();
Statement stmt = this.conn.createStatement();
stmt.executeUpdate(sql);
}
}
由此可见,其整个结构非常清晰,下面是MVC 中各个角色的对应组件。
M:Model,即模型,对应JavaBean 。
V: View ,即视图,对应JSP 页面。
C:Controller,即控制器,对应Servlet。