☰
Servlet技术入门:从HTTP请求到Java Web开发
2026/11/4 10:13:25 网站建设 项目流程

1. 从零认识Servlet技术栈

第一次接触Java Web开发时,那个黑乎乎的Tomcat控制台输出"Servlet initialized"让我愣了半天。作为承载Java Web二十余年的基石技术,Servlet的重要性怎么强调都不为过——它就像Web应用的神经系统,默默处理着每个HTTP请求的传导与响应。不同于Spring等框架的"开箱即用",理解Servlet能让你真正掌握Web请求的生命周期。

我在电商项目里就吃过亏:当时直接用Spring Boot跳过了Servlet学习,结果遇到Filter顺序问题时完全不知如何调试。后来花了两周时间补课,才发现很多"高级框架特性"其实都是Servlet规范的延伸。这次我们就用最接地气的方式,从HTTP协议与Servlet容器的协作关系讲起,手把手实现一个能处理表单提交的完整Servlet。

2. 环境准备与工具选型

2.1 JDK与IDE的选择困境

推荐使用JDK 11这个长期支持版本(LTS),避免最新版可能存在的兼容性问题。实测在JDK 17下某些旧版Tomcat会出现类加载警告。IDE方面:

  • Eclipse:传统选择,但需要手动配置Server Runtime
  • IntelliJ IDEA:社区版足够用,对Tomcat集成更好
  • VS Code + Java插件:轻量但需要自己配构建工具

个人推荐IDEA+Tomcat 9的组合,平衡了易用性和稳定性。注意避开Tomcat 10这个"踩坑重灾区"——它默认使用Jakarta EE命名空间,与javax.*的旧项目不兼容。

2.2 手动创建Web项目的意义

虽然Maven archetype能快速生成项目骨架,但建议第一次还是手动创建:

  1. 在IDEA新建空项目
  2. 添加web模块(记得勾选"Create web.xml")
  3. 创建WEB-INF和lib目录

这个过程中你会理解:

  • web.xml为什么放在WEB-INF下
  • 编译输出目录为什么是WEB-INF/classes
  • lib目录存放的jar包如何被加载

3. Servlet核心原理拆解

3.1 从HTTP到Servlet的旅程

当你在浏览器输入URL时:

  1. Tomcat的Connector接收到TCP数据包
  2. 解析成HttpServletRequest对象
  3. 根据web.xml映射找到对应的Servlet
  4. 调用service()方法生成响应

关键点在于:Servlet本身只是个接口,真正干活的是容器提供的实现类。我们写的MyServlet只是被容器调用的回调类。

3.2 生命周期方法实战

在自定义Servlet中重写这三个方法:

public class MyServlet extends HttpServlet { // 容器启动时执行(注意:不是每次请求!) @Override public void init() throws ServletException { System.out.println("正在初始化数据库连接..."); } // 处理GET请求 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { resp.getWriter().print("Hello from GET"); } // 容器关闭时执行 @Override public void destroy() { System.out.println("释放连接池资源..."); } }

常见误区:

  • init()里放业务逻辑(应该只做初始化)
  • 在service()中直接写响应(应该用doGet/doPost)
  • 忘记调用super.destroy()导致资源泄漏

4. 部署与调试技巧

4.1 热部署的三种方式

  1. 爆炸式部署:直接修改WEB-INF/classes下的class文件

    • 优点:即时生效
    • 缺点:容易导致类加载混乱
  2. 上下文重载:配置context.xml的reloadable="true"

    • 优点:自动检测变化
    • 缺点:消耗性能(实测CPU升高15%)
  3. JRebel插件:商业方案,修改代码自动热更新

    • 适合大型项目,但需要破解或付费

4.2 必知的调试技巧

  • 查看完整请求头:
Collections.list(req.getHeaderNames()) .forEach(name -> System.out.println(name + ": " + req.getHeader(name)));
  • 获取真实客户端IP(考虑代理情况):
String ip = req.getHeader("X-Forwarded-For") != null ? req.getHeader("X-Forwarded-For") : req.getRemoteAddr();
  • 强制浏览器不缓存响应:
resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");

5. 进阶实战:处理文件上传

5.1 表单配置要点

必须设置enctype属性:

<form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="myFile"> </form>

5.2 Servlet处理代码

使用Apache Commons FileUpload更可靠:

DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 内存缓冲区1MB ServletFileUpload upload = new ServletFileUpload(factory); upload.setSizeMax(50 * 1024 * 1024); // 限制50MB List<FileItem> items = upload.parseRequest(request); for (FileItem item : items) { if (!item.isFormField()) { File uploadedFile = new File("/path/to/save", item.getName()); item.write(uploadedFile); } }

5.3 常见坑点

  1. 忘记配置临时目录导致报错:
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
  1. 中文文件名乱码:
upload.setHeaderEncoding("UTF-8");
  1. 文件覆盖问题:
String fileName = UUID.randomUUID() + "_" + item.getName();

6. 性能优化方向

6.1 线程池配置

在server.xml中调整Connector参数:

<Connector executor="tomcatThreadPool" maxThreads="200" minSpareThreads="20" acceptCount="100" />

6.2 异步Servlet实践

适合长时间任务(如报表生成):

@WebServlet(urlPatterns = "/report", asyncSupported = true) public class ReportServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { AsyncContext ctx = req.startAsync(); executor.submit(() -> { // 模拟耗时操作 Thread.sleep(5000); ctx.getResponse().getWriter().print("Report generated"); ctx.complete(); }); } }

6.3 缓存策略实现

利用Last-Modified头实现客户端缓存:

long lastModified = getLastModifiedFromDB(); if (req.getDateHeader("If-Modified-Since") >= lastModified) { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } resp.setDateHeader("Last-Modified", lastModified);

7. 安全防护要点

7.1 SQL注入防御

错误的做法:

String sql = "SELECT * FROM users WHERE name='" + req.getParameter("name") + "'";

正确的参数化查询:

PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM users WHERE name=?"); stmt.setString(1, req.getParameter("name"));

7.2 XSS防护

输出编码处理:

import org.apache.commons.text.StringEscapeUtils; String safeOutput = StringEscapeUtils.escapeHtml4(userInput); resp.getWriter().print(safeOutput);

或者设置响应头:

resp.setHeader("X-XSS-Protection", "1; mode=block");

7.3 CSRF防护

添加Token验证:

// 生成Token String token = UUID.randomUUID().toString(); session.setAttribute("csrfToken", token); // 在表单中添加 <input type="hidden" name="csrfToken" value="${csrfToken}"> // 验证Token if (!session.getAttribute("csrfToken").equals(req.getParameter("csrfToken"))) { throw new ServletException("CSRF验证失败"); }

8. 现代演进方向

虽然现在流行Spring MVC等框架,但理解Servlet能让你:

  • 快速定位框架底层问题
  • 编写高性能Filter处理通用逻辑
  • 自定义协议支持(如WebSocket初始握手)

比如用Servlet实现简单的REST:

@WebServlet("/api/*") public class RestServlet extends HttpServlet { protected void service(HttpServletRequest req, HttpServletResponse resp) { String path = req.getPathInfo(); switch (path) { case "/users": if ("GET".equals(req.getMethod())) { // 查询用户逻辑 } break; // 其他路由... } } }

最后分享一个排查技巧:当遇到"404但路径明明正确"时,检查:

  1. web.xml的url-pattern是否带斜杠
  2. 项目Artifact的Context Path配置
  3. Tomcat的server.xml中Host的appBase路径

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询