1. Servlet基础概念与核心机制
Servlet作为JavaEE规范的核心组件,其本质是一个运行在Web服务器上的Java程序。与传统的CGI技术相比,Servlet采用线程池处理请求而非进程创建,这使得它在高并发场景下性能表现优异。当我们在Tomcat中部署一个Servlet时,容器会通过反射机制调用其无参构造函数完成实例化,这个生命周期仅发生一次。
Servlet的核心方法莫过于service(),它就像是一个交通警察,根据HTTP请求方法类型(GET/POST等)将请求分发给对应的doGet()或doPost()方法。我曾在一个电商项目中遇到性能瓶颈,通过重写service()方法实现请求分流,使QPS提升了近40%。这提醒我们:理解底层机制往往能带来意想不到的优化空间。
关键点:Servlet是单例多线程的,务必注意线程安全问题。实例变量需要特殊处理,建议使用ThreadLocal或方法内局部变量。
2. MVC模式的三层解耦实践
2.1 模型层的业务封装
模型层(Model)不应只是简单的数据容器。在图书管理系统案例中,我将业务规则(如借阅期限计算、库存校验)封装在Book模型的对应方法中,这使得控制器可以保持简洁。一个常见的误区是把模型写成贫血对象,这实际上违背了MVC的初衷。
2.2 控制器的路由优化
控制器(Controller)作为调度中心,最忌变成"上帝类"。我曾重构过一个2000行的Servlet控制器,通过命令模式将其拆分为多个Handler。例如:
// 优化前 protected void doPost(HttpServletRequest req...){ String action = req.getParameter("action"); if("addBook".equals(action)){ // 50行处理逻辑 } else if("queryBook".equals(action)){ // 80行处理逻辑 } } // 优化后 Map<String, ActionHandler> handlers = new HashMap<>(); handlers.put("addBook", new AddBookHandler()); handlers.put("queryBook", new QueryBookHandler()); protected void doPost(...){ handlers.get(req.getParameter("action")).handle(req,resp); }2.3 视图层的动态渲染
JSP作为传统视图层技术,其自定义标签库能有效减少脚本片段。在实现视频播放列表时,我结合JSTL的<c:forEach>和EL表达式,将后端传递的List
3. Servlet与JSP的协作陷阱
3.1 作用域对象的选用策略
四个作用域(page/request/session/application)的选择需要谨慎:
- 页面跳转用request.setAttribute()
- 用户会话数据用session.setAttribute()
- 全局配置用getServletContext().setAttribute()
我曾遇到一个内存泄漏案例:将10MB的报表数据误存到session而非request中,导致服务器内存快速耗尽。
3.2 中文乱码的终极解决方案
乱码问题困扰着许多初学者,这里给出完整解决方案:
// 请求编码(处理POST) request.setCharacterEncoding("UTF-8"); // 响应编码 response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); // JSP页面头部 <%@ page contentType="text/html;charset=UTF-8" %>3.3 转发与重定向的抉择
| 特性 | RequestDispatcher.forward() | response.sendRedirect() |
|---|---|---|
| URL变化 | 否 | 是 |
| 数据传递 | 通过request属性 | 需URL参数或session |
| 服务器负载 | 低 | 高(二次请求) |
| 典型场景 | 内部流程跳转 | 跨应用/登录后跳转 |
在支付流程中,forward适合处理银行接口回调,而redirect更适合支付完成后的结果页跳转。
4. 现代MVC框架的演进启示
虽然现在Spring MVC大行其道,但理解原生Servlet MVC仍具价值。以DispatcherServlet为例,其核心原理正是我们讨论的MVC模式升级版:
- 通过HandlerMapping实现请求到控制器的映射
- 采用适配器模式统一处理不同类型的控制器
- 视图解析器链支持多种视图技术
在2024年的新项目中,我建议:
- 中小型项目可用JFinal这类轻量框架
- 企业级应用首选Spring MVC
- 学习阶段务必手动实现至少一个Servlet MVC案例
5. 实战:构建图书管理模块
5.1 模型设计
public class Book { private String isbn; private String title; private List<String> authors; // 业务方法示例 public boolean isAvailable() { return borrowRecords.stream() .noneMatch(r -> r.getReturnDate() == null); } }5.2 控制器实现
@WebServlet("/book") public class BookServlet extends HttpServlet { private BookService service = new BookServiceImpl(); protected void doGet(...) { String id = request.getParameter("id"); Book book = service.findById(id); request.setAttribute("book", book); request.getRequestDispatcher("/bookDetail.jsp").forward(...); } }5.3 视图渲染
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <div class="book-info"> <h2>${book.title}</h2> <c:forEach items="${book.authors}" var="author"> <span class="badge">${author}</span> </c:forEach> <p>库存状态: ${book.available ? '可借阅' : '已借出'}</p> </div>6. 性能优化备忘录
Servlet线程池配置(Tomcat为例):
<Connector port="8080" maxThreads="200" minSpareThreads="20" acceptCount="100"/>JSP预编译:生产环境建议用JSPC编译器提前生成Servlet类
静态资源处理:配置DefaultServlet的缓存策略
<Context cachingAllowed="true" cacheTTL="86400000"/>会话优化:对只读操作设置session="false"
<%@ page session="false" %>
在最近的压力测试中,通过调整线程池参数配合NIO连接器,系统成功支撑了每秒5000+的并发请求。这印证了基础架构优化的重要性——再好的架构设计也需要合理的参数调优。