HttpServlet API 介绍

在上篇笔记中简单提到了一下 HttpServlet 这个类,它是 Servlet 的一个针对于 Http 协议的实现类。HttpServlet 的父类是 GenericServlet,GenericServlet 是一个通用的、与网络协议无关的 Servlet 实现类。由于我们接触最多的就是使用 Http 协议进行网络通信,所以将重点放在 HttpServlet 上。

下面介绍 HttpServlet 的相关方法:

void init(ServletConfig config) void init()

这两个init 方法继承自它的父类。带参数的init 方法是实现 Servlet 接口所定义的抽象方法。不带参数的 init 方法则是他的重载形式。关于带参数的init 方法,在 Servlet 中已经提到,这里不再重复。这两个初始化方法的区别在于,当我们需要做自己的初始化工作(如连接数据库等)时,如果使用有参的 init 方法,必须在自己的初始化代码前手动加上 super.init(config); 这一行代码,否则在 Servlet 组件初始化时,无法接收来自Web容器的 ServletConfig 对象。而如果使用无参的 init 方法,则可以直接写自己的初始化代码。如:

Override
public void init() throws ServletException {
	//连接数据库的代码
}
@Override
public void init(ServletConfig config) throws ServletException {
	super.init(config);
	//连接数据库的代码
}

在 HttpServlet 父类 GenericServlet 的源码中,这两个方法是这样的:

public void init(ServletConfig config)
        throws ServletException {
    this.config = config;
    init();
}
public void init()
        throws ServletException {
}

在有参的 init 方法中调用了无参的 init 方法,因此我们重写无参 init 方法时无需再显式调用父类中的实现。

void service(ServletRequest req, ServletResponse res) void service(HttpServletRequest req, HttpServletResponse resp)

HttpServlet 中有两个 service 方法,我们还是看看源码中这两个方法时如何实现的。

    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest request;
        HttpServletResponse response;
        try
        {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        }
        catch(ClassCastException e)
        {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }

从上面的代码中可以看到在参数类型分别为第一个 service 方法中,其实是将两个参数分别进行向下转型,将 ServletRequest、ServletResponse 类型的参数分别转换为 HttpServletRequest 和 HttpServletResponse,然后调用第二个 service 方法。所以我们自己的 Servlet 在覆盖 service 方法时只需要覆盖第二个即可。

void doGet(HttpServletRequest req, HttpServletResponse resp) void doPost(HttpServletRequest req, HttpServletResponse resp)

在 HttpServlet 中这样以 do 开头的方法共有7个,分别对应 Http 协议中 7 种请求方式。我们最常使用的是 GET 和 POST 方法,所以这里只列出这两个方法。顾名思义,doGet 方法就是专门处理 GET 方式的网络请求,doPost 方法就是专门处理 POST 方式的网络请求。但是在 Servlet 中处理请求的是 service 方法,那么它和这些 doXXX 方法之间有什么联系呢?我们看看源码就可以知道了。

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();
        if(method.equals("GET"))
        {
            long lastModified = getLastModified(req);
            if(lastModified == -1L)
            {
                doGet(req, resp);
            } else
            {
                long ifModifiedSince;
                try
                {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                }
                catch(IllegalArgumentException iae)
                {
                    ifModifiedSince = -1L;
                }
                if(ifModifiedSince < (lastModified / 1000L) * 1000L)
                {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else
                {
                    resp.setStatus(304);
                }
            }
        } else
        if(method.equals("HEAD"))
        {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else
        if(method.equals("POST"))
            doPost(req, resp);
        else
        if(method.equals("PUT"))
            doPut(req, resp);
        else
        if(method.equals("DELETE"))
            doDelete(req, resp);
        else
        if(method.equals("OPTIONS"))
            doOptions(req, resp);
        else
        if(method.equals("TRACE"))
        {
            doTrace(req, resp);
        } else
        {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object errArgs[] = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

可以看到,在第二个 service 方法中根据请求的方式,来对请求进行了转发。如果请求方法为 GET,就调用 doGet 方法进行处理,如果请求方式为 POST,则调用 doPost 方法来处理。所以我们自己的 Servlet 中,可以根据不同的请求方式,在相应的 do 方法中编写处理逻辑,而不用覆盖 service 方法。

之前向客户端返回一段字符串的 Servlet 示例也可以这样来实现:

public class HelloServlet extends HttpServlet{
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
			throws ServletException, IOException {
		PrintWriter out = resp.getWriter();
		out.println("Hello World.");
	}
}

注意:上述提到的几个方法都有可能会出现异常,我们覆盖相应方法时需要在参数列表后 throws(抛出)这些异常。不然就需要在方法中使用 try-catch 结构来捕获异常。(通常 IDE 都会自动 throws 异常)

String getInitParameter(String name) Enumeration< String> getInitParameterNames() ServletContext getServletContext() String getServletName()

上面四个方法同样继承自它的父类,但实际上这四个方法是 ServletConfig 中的方法。我们看看其中 getServletContext 的源码实现:

public ServletContext getServletContext() {
    return getServletConfig().getServletContext();
}

可以看到,GenericServlet 中的 getServletContext 方法,是通过调用它的成员变量 ServletConfig 对象的 getServletContext 来实现的。这样做的好处在于我们可以直接在 Servlet 类中调用到 ServletConfig 的各个方法,更加方便。

其他的一些方法如 destroy、getServletConfig、getServletInfo 等之前的笔记中已经进行了说明,这里就不一一列举。

POST 实例

下面,我们写一个关于 POST 请求的实例,模仿网站的登录功能。

1.在项目中新建一个 HTML 文件(暂时还没学到 JSP,所以没有用它),在 Eclipse 中这个文件通常放在 WebContent 这个文件夹下。index.html 中代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="login" method="POST">
		username:<input type="text" name="username"><br/>
		password:<input type="password" name="password"><br/>
		<input type="submit" value="登录">
	</form>
</body>
</html>

网页中有一个表单,其中有用户名和密码的输入框和登录按钮。效果如图:

img1.png

2.新建 LoginServlet,继承自 HttpServlet。获取到登录的用户名和密码,打印在控制台上,并在页面中显示欢迎语。代码如下:

public class LoginServlet extends HttpServlet{
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
			throws ServletException, IOException {
		String name = req.getParameter("username");
		String password = req.getParameter("password");
		System.out.println("name = "+name);
		System.out.println("password = "+password);
		PrintWriter out = resp.getWriter();
		out.println("Welcome,"+name);
	}
}

注意:我们这里需要使用 doPost 方法,因为我们在 HTML 文件中将表单的请求方式设置成了 POST。另外,调用 req.getParameter 方法时,传进去的参数名要和 form 表单中 input 标签的 name 一致。

3.配置 web.xml 文件,注册 LoginServlet 并添加 URL 映射。

	<servlet>
		<servlet-name>LoginServlet</servlet-name>
		<servlet-class>cc.lixiaoyu.helloweb.LoginServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>LoginServlet</servlet-name>
		<url-pattern>/login</url-pattern>
	</servlet-mapping>

这里需要注意的一个地方在于 url-pattern 的值需要和 HTML 文件里 form 表单的 action 的值一致,否则无法映射成功。

4.运行。我们将项目运行起来,在 HTML 页面中输入用户名 Owen,密码 123456,点击登录。实际效果如图:

img2.png

结语

这篇笔记介绍了 HttpServlet 的相关方法,用源码解释了一些现象,并实现了一个简易的用户登录示例。这篇笔记就到这里,下篇笔记见。