一、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生命周期
- 加载阶段:容器扫描web.xml,识别类路径
- 初始化:执行init()方法(可覆盖配置)
- 服务阶段:多线程处理doGet/doPost等请求
- 销毁:服务器关闭时调用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:确保三处编码设置:
- response.setContentType("text/html;charset=UTF-8")
- response.setCharacterEncoding("UTF-8")
- 服务器容器启动参数增加-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的情况变少了,但它在以下场景仍是首选:
- 开发轻量级中间件
- 编写高性能代理服务
- 构建定制化协议网关
- 教学原理性知识体系
随着云原生技术的发展,Servlet的生命周期管理特性在容器化部署中展现出独特优势。理解Servlet本质,就如同掌握了Web应用的基因编码,能够帮助我们更自如地穿梭在各个框架之间。
评论