今天我们就抛弃 IDE、拒绝框架,自己纯手工编写一个 Servlet,并在 Tomcat 中运行起来。一方面进一步加深对 Servlet 的理解;另一方面,还可以熟悉一下 Tomcat 的基本功能使用。

主要的步骤有:

  1. 下载并安装 Tomcat。
  2. 编写一个继承 HttpServlet 的 Java 类。
  3. 将 Java 类文件编译成 Class 文件。
  4. 建立 Web 应用的目录结构,并配置 web.xml。
  5. 部署 Web 应用。
  6. 启动 Tomcat。
  7. 浏览器访问验证结果。
  8. 查看 Tomcat 日志。

动手时间

  1. 安装 tomcat

/bin:存放 Windows 或 Linux 平台上启动和关闭 Tomcat 的脚本文件。
/conf:存放 Tomcat 的各种全局配置文件,其中最重要的是 server.xml。
/lib:存放 Tomcat 以及所有 Web 应用都可以访问的 JAR 文件。
/logs:存放 Tomcat 执行时产生的日志文件。
/work:存放 JSP 编译后产生的 Class 文件。
/webapps:Tomcat 的 Web 应用目录,默认情况下把 Web 应用放在这个目录下。

  1. 编写一个继承 HttpServlet 的 Java 类
    javax.servlet 包提供了实现 Servlet 接口的 GenericServlet 抽象类。这是一个比较方便的类,可以通过扩展它来创建 Servlet。但是大多数的 Servlet 都在 HTTP 环境中处理请求,因此 Serve 规范还提供了 HttpServlet 来扩展 GenericServlet 并且加入了 HTTP 特性。我们通过继承 HttpServlet 类来实现自己的 Servlet 只需要重写两个方法:doGet 和 doPost。
    因此今天我们创建一个 Java 类去继承 HttpServlet 类,并重写 doGet 和 doPost 方法。首先新建一个名为 MyServlet.java 的文件,敲入下面这些代码:
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
 
public class MyServlet extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        System.out.println("MyServlet 在处理 get()请求...");
        PrintWriter out = response.getWriter();
        response.setContentType("text/html;charset=utf-8");
        out.println("<strong>My Servlet!</strong><br>");
    }
 
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        System.out.println("MyServlet 在处理 post()请求...");
        PrintWriter out = response.getWriter();
        response.setContentType("text/html;charset=utf-8");
        out.println("<strong>My Servlet!</strong><br>");
    }
 
}

这个 Servlet 完成的功能很简单,分别在 doGet 和 doPost 方法体里返回一段简单的 HTML。

  1. 将 Java 文件编译成 Class 文件
javac -cp ./servlet-api.jar MyServlet.java

编译成功后,你会在当前目录下找到一个叫 MyServlet.class 的文件。

  1. 建立 Web 应用的目录结构
    Servlet 是放到 Web 应用部署到 Tomcat 的,而 Web 应用具有一定的目录结构,所有我们按照要求建立 Web 应用文件夹,名字叫 MyWebApp,然后在这个目录下建立子文件夹,像下面这样:
MyWebApp/WEB-INF/web.xml
 
MyWebApp/WEB-INF/classes/MyServlet.class

然后在 web.xml 中配置 Servlet,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0"
  metadata-complete="true">
 
    <description> Servlet Example. </description>
    <display-name> MyServlet Example </display-name>
    <request-character-encoding>UTF-8</request-character-encoding>
 
    <servlet>
      <servlet-name>myServlet</servlet-name>
      <servlet-class>MyServlet</servlet-class>
    </servlet>
 
    <servlet-mapping>
      <servlet-name>myServlet</servlet-name>
      <url-pattern>/myservlet</url-pattern>
    </servlet-mapping>
 
</web-app>

你可以看到在 web.xml 配置了 Servlet 的名字和具体的类,以及这个 Servlet 对应的 URL 路径。请你注意,servlet 和 servlet-mapping 这两个标签里的 servlet-name 要保持一致。

  1. 部署 Web 应用
    Tomcat 应用的部署非常简单,将这个目录 MyWebApp 拷贝到 Tomcat 的安装目录下的 webapps 目录即可。
  2. 启动 Tomcat
    找到 Tomcat 安装目录下的 bin 目录,根据操作系统的不同,执行相应的启动脚本。如果是 Windows 系统,执行<font style="color:rgb(53, 53, 53);">startup.bat</font>.;如果是 Linux 系统,则执行<font style="color:rgb(53, 53, 53);">startup.sh</font>。
  3. 验证结果
    ****访问:
    URL:<font style="color:rgb(53, 53, 53);">http://localhost:8080/MyWebApp/myservlet</font>
    这里需要注意,访问 URL 路径中的 MyWebApp 是 Web 应用的名字,myservlet 是在 web.xml 里配置的 Servlet 的路径。
  4. 查看 Tomcat 日志

打开 Tomcat 的日志目录,也就是 Tomcat 安装目录下的 logs 目录。Tomcat 的日志信息分为两类 :一是运行日志,它主要记录运行过程中的一些信息,尤其是一些异常错误日志信息 ;二是访问日志,它记录访问的时间、IP 地址、访问的路径等相关信息。

这里简要介绍各个文件的含义。

  • <font style="color:rgb(53, 53, 53);">catalina.***.log</font>

主要是记录 Tomcat 启动过程的信息,在这个文件可以看到启动的 JVM 参数以及操作系统等日志信息。

  • <font style="color:rgb(53, 53, 53);">catalina.out</font>

catalina.out 是 Tomcat 的标准输出(stdout)和标准错误(stderr),这是在 Tomcat 的启动脚本里指定的,如果没有修改的话 stdout 和 stderr 会重定向到这里。所以在这个文件里可以看到我们在 MyServlet.java 程序里打印出来的信息:

MyServlet 在处理 get() 请求…

  • <font style="color:rgb(53, 53, 53);">localhost.**.log</font>

主要记录 Web 应用在初始化过程中遇到的未处理的异常,会被 Tomcat 捕获而输出这个日志文件。

  • <font style="color:rgb(53, 53, 53);">localhost_access_log.**.txt</font>

存放访问 Tomcat 的请求日志,包括 IP 地址以及请求的路径、时间、请求协议以及状态码等信息。

  • <font style="color:rgb(53, 53, 53);">manager.***.log/host-manager.***.log</font>

存放 Tomcat 自带的 manager 项目的日志信息。

用注解试试看

用注解的方式部署 Servlet

我们首先修改 Java 代码,给 Servlet 类加上@WebServlet注解,修改后的代码如下。

import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@WebServlet("/myAnnotationServlet")
public class AnnotationServlet extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
   System.out.println("AnnotationServlet 在处理 get()请求...");
        PrintWriter out = response.getWriter();
        response.setContentType("text/html; charset=utf-8");
        out.println("<strong>Annotation Servlet!</strong><br>");
 
    }
 
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        System.out.println("AnnotationServlet 在处理 post()请求...");
        PrintWriter out = response.getWriter();
        response.setContentType("text/html; charset=utf-8");
        out.println("<strong>Annotation Servlet!</strong><br>");
 
    }
 
}  

这段代码里最关键的就是这个注解,它表明两层意思:第一层意思是 AnnotationServlet 这个 Java 类是一个 Servlet,第二层意思是这个 Servlet 对应的 URL 路径是 myAnnotationServlet。

@WebServlet("/myAnnotationServlet")

创建好 Java 类以后,同样经过编译,并放到 MyWebApp 的 class 目录下。这里要注意的是,你需要删除原来的 web.xml,因为我们不需要 web.xml 来配置 Servlet 了。然后重启 Tomcat,接下来我们验证一下这个新的 AnnotationServlet 有没有部署成功。在浏览器里输入:<font style="color:rgb(53, 53, 53);">http://localhost:8080/MyWebApp/myAnnotationServlet</font>,得到结果:Annotation Servlet!

这说明我们的 AnnotationServlet 部署成功了。可以通过注解完成 web.xml 所有的配置功能,包括 Servlet 初始化参数以及配置 Filter 和 Listener 等。

我的 tiny-tomcat

put 和 get 中 header 数量的对比

几个问题

  1. Servlet 3.0 规范支持用注解的方式来部署 Servlet,不需要在 web.xml 里配置,而如果要注解和 web.xml 一起用,需要将 web.xml中的配置 metadata-complete="true", 你需要把它设置成 metadata-complete="false"。
  2. response.setContentType(“text/html;charset=utf-8”)发现中文输出还是乱码

调下顺序,像下面这样:
response.setContentType(“text/html; charset=utf-8”);
PrintWriter out = response.getWriter();

getWrite的源码如下:

public PrintWriter getWriter()
throws IOException {

if (usingOutputStream) {
    throw new IllegalStateException
    (sm.getString("coyoteResponse.getWriter.ise"));
}

if (ENFORCE_ENCODING_IN_GET_WRITER) {
    /*
         * If the response's character encoding has not been specified as
         * described in <code>getCharacterEncoding</code> (i.e., the method
         * just returns the default value <code>ISO-8859-1</code>),
         * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
         * (with the effect that a subsequent call to getContentType() will
         * include a charset=ISO-8859-1 component which will also be
         * reflected in the Content-Type response header, thereby satisfying
         * the Servlet spec requirement that containers must communicate the
         * character encoding used for the servlet response's writer to the
         * client).
         */
    setCharacterEncoding(getCharacterEncoding());
}

usingWriter = true;
outputBuffer.checkConverter();
if (writer == null) {
    writer = new CoyoteWriter(outputBuffer);
}
return writer;
}

你看注释里它说:如果调这个方法之前没有指定Response的字符编码,就用默认的ISO-8859-1,ISO-8859-1不包括中文字符。

  1. 和 spring mvc 中有什么关系呢?

Tomcat的Wrapper组件-Filter-DispatcherServlet-Controller