1. 概述

在这篇文章中,我们将探讨Java web开发的核心部分——Servlet。

2. Servlet与容器

简单来说,Servlet就是一个处理请求、处理这些请求并返回响应的类。

例如,我们可以使用Servlet通过HTML表单收集用户输入,查询数据库中的记录,并动态创建网页。

Servlet受一个名为Servlet容器的Java应用程序控制。当运行在Web服务器上的应用程序接收到请求时,服务器会将请求传递给Servlet容器,后者再将请求转发到目标Servlet。

3. Maven依赖

要在我们的web应用中添加Servlet支持,需要添加javax.servlet-api依赖:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

最新的Maven依赖可以在这里找到。

当然,我们还需要配置一个Servlet容器来部署我们的应用;这里是如何在Tomcat上部署WAR文件的一个好起点

4. Servlet生命周期

让我们来看看定义Servlet生命周期的一系列方法。

4.1. init()

init()方法设计成只被调用一次。如果Servlet实例不存在,Web容器会:

  1. 加载Servlet类
  2. 创建Servlet类的实例
  3. 调用init()方法初始化它

init()方法必须成功完成,Servlet才能接收任何请求。如果init()方法抛出ServletException或在Web服务器定义的时间范围内未返回,Servlet容器无法将Servlet投入服务。

public void init() throws ServletException {
    // Initialization code like set up database etc....
}

4.2. service()

init()方法成功完成后,才会调用这个方法。

容器调用service()方法来处理来自客户端的请求,解析HTTP请求类型(如GET、POST、PUT、DELETE等),然后根据需要调用doGetdoPostdoPutdoDelete等方法。

public void service(ServletRequest request, ServletResponse response) 
  throws ServletException, IOException {
    // ...
}

4.3. destroy()

由Servlet容器调用以将Servlet移出服务。

这个方法只在Servlet的service()方法中的所有线程退出或超时后调用。容器调用此方法后,将不会再对Servlet调用service()方法。

public void destroy() {
    // 
}

5. 示例Servlet

首先,为了将上下文根从javax-servlets-1.0-SNAPSHOT更改为/,请在$CATALINA_HOME\conf\server.xmlHost标签下添加:

<Context path="/" docBase="javax-servlets-1.0-SNAPSHOT"></Context>

现在,让我们设置一个完整的示例,展示如何使用表单处理信息。

首先,定义一个映射为*/calculateServlet*的Servlet,它将捕获表单POST的信息并使用RequestDispatcher返回结果:

@WebServlet(name = "FormServlet", urlPatterns = "/calculateServlet")
public class FormServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, 
      HttpServletResponse response)
      throws ServletException, IOException {

        String height = request.getParameter("height");
        String weight = request.getParameter("weight");

        try {
            double bmi = calculateBMI(
              Double.parseDouble(weight), 
              Double.parseDouble(height));
            
            request.setAttribute("bmi", bmi);
            response.setHeader("Test", "Success");
            response.setHeader("BMI", String.valueOf(bmi));

            request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request, response);
        } catch (Exception e) {
           request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request, response);
        }
    }

    private Double calculateBMI(Double weight, Double height) {
        return weight / (height * height);
    }
}

如上所述,标记为@WebServlet的类必须继承javax.servlet.http.HttpServlet类。值得注意的是,@WebServlet注解仅从Java EE 6开始可用。

@WebServlet注解在部署时由容器处理,并在指定的URL模式下提供对应的Servlet。通过注解定义URL模式,我们可以避免使用名为web.xml的传统部署描述符来映射Servlet。

如果我们希望不使用注解映射Servlet,可以使用传统的web.xml

<web-app ...>

    <servlet>
       <servlet-name>FormServlet</servlet-name>
       <servlet-class>com.root.FormServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FormServlet</servlet-name>
        <url-pattern>/calculateServlet</url-pattern>
    </servlet-mapping>

</web-app>

接下来,创建一个基本的HTML表单:

<form name="bmiForm" action="calculateServlet" method="POST">
    <table>
        <tr>
            <td>Your Weight (kg) :</td>
            <td><input type="text" name="weight"/></td>
        </tr>
        <tr>
            <td>Your Height (m) :</td>
            <td><input type="text" name="height"/></td>
        </tr>
        <th><input type="submit" value="Submit" name="find"/></th>
        <th><input type="reset" value="Reset" name="reset" /></th>
    </table>
    <h2>${bmi}</h2>
</form>

最后——为了确保一切按预期工作,我们也编写一个快速测试:

public class FormServletLiveTest {

    @Test
    public void whenPostRequestUsingHttpClient_thenCorrect() 
      throws Exception {

        HttpClient client = new DefaultHttpClient();
        HttpPost method = new HttpPost(
          "http://localhost:8080/calculateServlet");

        List<BasicNameValuePair> nvps = new ArrayList<>();
        nvps.add(new BasicNameValuePair("height", String.valueOf(2)));
        nvps.add(new BasicNameValuePair("weight", String.valueOf(80)));

        method.setEntity(new UrlEncodedFormEntity(nvps));
        HttpResponse httpResponse = client.execute(method);

        assertEquals("Success", httpResponse
          .getHeaders("Test")[0].getValue());
        assertEquals("20.0", httpResponse
          .getHeaders("BMI")[0].getValue());
    }
}

6. Servlet、HttpServlet和JSP

重要的是要理解,Servlet技术并不局限于HTTP协议。

实际上,几乎总是如此,但Servlet是一个通用接口,而HttpServlet是该接口的扩展,增加了HTTP特定的支持,如doGetdoPost等。

最后,Servlet技术也是其他许多web技术的主要驱动,如JavaServer Pages (JSP),Spring MVC等。

7. 总结

在这篇简短的文章中,我们介绍了Java web应用中Servlet的基础知识。

示例项目可以直接下载并运行,作为GitHub项目:https://github.com/eugenp/tutorials/tree/master/web-modules/javax-servlets