一、Servlet的前世今生

当我们端着咖啡坐在电脑前准备开发网站时,服务器就像一家全天候营业的数码餐厅。Servlet就是这家餐厅里最敬业的"金牌服务员",它专门处理来自客户端(如浏览器)的"点餐请求"。1997年Sun公司推出的这套技术规范,至今仍在企业级应用开发中扮演重要角色。

在Spring框架大行其道的今天,学习Servlet就像学习汽车原理时先要熟悉发动机。它直接建立在HTTP协议之上,能让我们看清Web开发最本质的运作规律。当我们用浏览器访问一个地址时,其实就开启了客户端与服务端的"传纸条"游戏:

用户点击 -> 网络请求 -> 服务器接收 -> Servlet处理 -> 生成响应 -> 返回结果

二、第一个Servlet程序

(Tomcat 10.1 + Java 17)

2.1 项目脚手架搭建

├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── demo
│       │           └── HelloServlet.java
│       └── webapp
│           └── WEB-INF
│               └── web.xml
└── pom.xml

2.2 HelloServlet实现类

import java.io.*;
import jakarta.servlet.*;
import jakarta.servlet.http.*;

// 必须继承HttpServlet才能获得web超能力
public class HelloServlet extends HttpServlet {

    // 用时间戳当身份证,观察服务器重启时的变化
    private final String birthCert = String.valueOf(System.currentTimeMillis());

    // 覆盖处理GET请求的方法
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response)
            throws ServletException, IOException {
        
        // 设置返回内容的类型和编码
        response.setContentType("text/html;charset=UTF-8");
        
        // 获取输出流开始写作
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head><title>来自2050年的问候</title></head>");
        out.println("<body>");
        out.println("<h1>Servlet说:世界你好!" + birthCert + "</h1>");
        out.println("</body></html>");
    }
}

2.3 web.xml配置(部署描述符)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                             https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <!-- 为Servlet起个花名 -->
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.demo.HelloServlet</servlet-class>
    </servlet>

    <!-- 配置访问路线 -->
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/greeting</url-pattern>
    </servlet-mapping>
</web-app>

启动Tomcat后访问http://localhost:8080/your-webapp/greeting,就会看到包含时间戳的动态页面。这里我们发现了Servlet的一个重要特性:单例多线程。birthCert字段在服务器重启前始终不变,说明所有请求共享同一个Servlet实例。

三、关联技术深潜:Filter的安检通道

3.1 过滤请求日志记录器

public class AccessLogger implements Filter {
    @Override
    public void doFilter(ServletRequest request, 
                        ServletResponse response,
                        FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest) request;
        System.out.printf("[%s] %s 进入安检通道%n", 
                new Date(), req.getRemoteAddr());

        // 放行到下一个处理节点
        chain.doFilter(request, response);

        System.out.printf("[%s] %s 完成安检%n", 
                new Date(), req.getRemoteAddr());
    }
}

3.2 过滤器配置

<filter>
    <filter-name>logger</filter-name>
    <filter-class>com.demo.AccessLogger</filter-class>
</filter>
<filter-mapping>
    <filter-name>logger</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

现在每个请求都会在控制台留下"进出记录",这种AOP式的设计可以统一处理编码设置、权限校验等通用逻辑。Filters就像机场的安检系统,在请求到达目标Servlet前进行层层检查。

四、Servlet生命周期

  1. 加载阶段:容器扫描web.xml,识别类路径
  2. 初始化:执行init()方法(可覆盖配置)
  3. 服务阶段:多线程处理doGet/doPost等请求
  4. 销毁:服务器关闭时调用destroy()方法

通过覆盖生命周期方法可以实现数据库连接池管理:

public class ResourceServlet extends HttpServlet {
    private DataSource dataSource;

    @Override
    public void init() throws ServletException {
        // 模拟连接池初始化
        dataSource = createDataSource();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        try (Connection conn = dataSource.getConnection()) {
            // 使用数据库连接...
        }
    }

    @Override
    public void destroy() {
        // 优雅关闭连接池
        shutdownDataSource(dataSource);
    }
}

五、技术选型深度解析

5.1 应用场景

  • 电商网站商品详情页的动态渲染
  • 企业OA系统的权限控制模块
  • API网关的请求路由分发
  • 文件上传下载的流式处理

5.2 优势亮点

  • 高性能基础:直接与TCP层交互,无额外抽象损耗
  • 灵活可控:深度掌控请求响应全流程
  • 生态基石:Spring MVC等框架底层均基于Servlet
  • 协议透明:原生的HTTP请求响应对象模型

5.3 使用注意

  • 线程安全:避免在service方法中修改成员变量
  • 资源释放:确保数据库连接在finally块中关闭
  • 编码规范:统一设置request/response的字符集
  • 异常处理:配置自定义错误页面防止信息泄露

六、经典问题排雷指南

Q:页面出现中文乱码? A:确保三处编码设置:

  1. response.setContentType("text/html;charset=UTF-8")
  2. response.setCharacterEncoding("UTF-8")
  3. 服务器容器启动参数增加-Dfile.encoding=UTF-8

Q:为什么我的Servlet没有被调用? 检查清单:

  • web.xml是否正确配置servlet-mapping
  • 项目是否部署到正确的Context Path
  • URL是否完全匹配(区分大小写)
  • 服务器日志是否报ClassNotFoundException

Q:如何处理文件上传? 正确示范:

@MultipartConfig
public class UploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, 
                        HttpServletResponse response) {
        Part filePart = request.getPart("file");
        InputStream fileContent = filePart.getInputStream();
        // 处理文件流...
    }
}

七、通向未来的路

虽然在现代开发中直接使用Servlet的情况变少了,但它在以下场景仍是首选:

  1. 开发轻量级中间件
  2. 编写高性能代理服务
  3. 构建定制化协议网关
  4. 教学原理性知识体系

随着云原生技术的发展,Servlet的生命周期管理特性在容器化部署中展现出独特优势。理解Servlet本质,就如同掌握了Web应用的基因编码,能够帮助我们更自如地穿梭在各个框架之间。