1. Servlet生命周期从出生到退休的完整旅程第一次接触Servlet时我完全被它的生命周期搞晕了。直到把Tomcat比作餐厅Servlet比作厨师才恍然大悟——init()是厨师上岗培训service()是日常做菜destroy()是退休离职。这个类比让我瞬间理解了Servlet的工作机制。Servlet的生命周期严格遵循三个阶段初始化阶段当Tomcat第一次收到某个Servlet的请求时会调用init()方法。这个方法在整个生命周期中只执行一次就像厨师的入职培训。我常在这里加载配置文件或建立数据库连接public void init(ServletConfig config) throws ServletException { // 读取web.xml中的初始化参数 String dbUrl config.getInitParameter(database_url); // 建立数据库连接池 connectionPool new ConnectionPool(dbUrl); }服务阶段每次请求都会触发service()方法。Tomcat会创建新的request和response对象传入就像顾客点单时给厨师递菜单。这里要注意线程安全问题protected void service(HttpServletRequest req, HttpServletResponse resp) { // 错误示范在方法内定义共享变量 int visitCount 0; // 这个会被多个线程同时修改 // 正确做法使用局部变量或同步块 synchronized(this) { totalVisits; // 线程安全的计数器 } }销毁阶段服务器关闭时调用destroy()方法。这里要优雅地释放资源就像厨师下班前要收拾厨房public void destroy() { // 关闭数据库连接池 connectionPool.close(); // 清理临时文件 tempFileCleaner.cleanUp(); }常见坑点很多同学在init()里初始化耗时资源导致应用启动缓慢。我的经验是使用懒加载第一次请求时再初始化非关键资源。2. JSP九大内置对象你的随身工具包刚开始用JSP时我总在纠结该用request还是session。直到把Web应用比作商场购物才明白request是购物篮一次结账就清空session是会员卡多次消费都有效application是商场公告栏所有顾客可见。核心四大作用域对比对象类比有效范围典型应用场景pageContext收银小票当前页面页面内变量传递request购物篮同一次请求表单提交、请求转发session会员卡同一次会话用户登录状态application商场公告栏整个应用网站访问计数器高频方法实战处理表单时我常用request的这几个方法% // 获取单个参数 String username request.getParameter(user); // 获取复选框多个值 String[] hobbies request.getParameterValues(hobby); // 设置属性供转发使用 request.setAttribute(welcomeMsg, Hello username); %登录验证时session的典型用法% // 存储用户信息 session.setAttribute(currentUser, userObj); // 设置30分钟超时 session.setMaxInactiveInterval(1800); // 退出时清空session session.invalidate(); %易错点很多同学分不清sendRedirect和forward。记住重定向是两次请求地址栏变session有效转发是一次请求地址栏不变request有效。3. 会话管理Cookie与Session的默契配合有次做购物车功能我完全依赖Session存储结果用户清理Cookie后购物车就空了。这才明白Session依赖名为JSESSIONID的Cookie两者必须配合使用。Cookie实战要点// 创建Cookie Cookie userCookie new Cookie(lastVisit, new Date().toString()); // 设置7天有效期 userCookie.setMaxAge(60 * 60 * 24 * 7); // 设置路径使全站可用 userCookie.setPath(/); // 添加到响应 response.addCookie(userCookie);Session底层原理首次请求时服务器创建Session并生成ID通过Set-Cookie头将JSESSIONID发给浏览器后续请求浏览器自动携带这个Cookie服务器根据ID找到对应的Session对象安全增强技巧// 防止Session固定攻击 request.getSession().invalidate(); // 先使旧session失效 HttpSession newSession request.getSession(true); // 创建新session // 重要操作前验证session新鲜度 long createTime session.getCreationTime(); if(System.currentTimeMillis() - createTime 1000*60*5) { // 超过5分钟的session需要重新登录 }性能优化对于大型应用可以将会话数据存储在Redis等缓存中减轻服务器内存压力。4. JDBC进阶从基础操作到连接池优化早期项目我直接用DriverManager当并发量上来后数据库连接成了瓶颈。改用连接池后性能提升了3倍这让我深刻认识到资源管理的重要性。传统JDBC流程// 1. 注册驱动新版可省略 Class.forName(com.mysql.cj.jdbc.Driver); // 2. 获取连接 Connection conn DriverManager.getConnection( jdbc:mysql://localhost:3306/mydb?useSSLfalse, user, pass); // 3. 创建PreparedStatement String sql SELECT * FROM users WHERE age ?; PreparedStatement stmt conn.prepareStatement(sql); stmt.setInt(1, 18); // 4. 执行查询 ResultSet rs stmt.executeQuery(); // 5. 处理结果 while(rs.next()) { System.out.println(rs.getString(username)); } // 6. 关闭资源倒序关闭 rs.close(); stmt.close(); conn.close(); // 重要不关闭会导致连接泄漏Druid连接池配置!-- web.xml中配置 -- context-param param-namedruid.url/param-name param-valuejdbc:mysql://localhost:3306/mydb/param-value /context-param context-param param-namedruid.maxActive/param-name param-value20/param-value /context-paramPreparedStatement防注入示例// 危险容易被SQL注入 String sql SELECT * FROM users WHERE username name ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); // 安全做法 String sql SELECT * FROM users WHERE username?; PreparedStatement stmt conn.prepareStatement(sql); stmt.setString(1, name); // 自动处理特殊字符性能对比测试显示连接池方案比传统方式QPS提升5倍PreparedStatement比Statement快30%。5. MVC设计模式分层解耦的艺术第一次参与团队项目时所有逻辑都写在JSP里改个按钮颜色都要排查半天。采用MVC分层后前端改动再也不会影响后台逻辑维护效率直线上升。经典MVC组件分工ModelJavaBean负责数据存取和业务逻辑public class UserService { public User login(String name, String pwd) { // 验证逻辑... return user; } }ViewJSP专注页面展示% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:if test${not empty user} 欢迎, ${user.name}! /c:ifControllerServlet处理请求流转protected void doPost(HttpServletRequest req, HttpServletResponse resp) { String name req.getParameter(username); String pwd req.getParameter(password); User user new UserService().login(name, pwd); if(user ! null) { req.getSession().setAttribute(user, user); req.getRequestDispatcher(/welcome.jsp).forward(req, resp); } else { resp.sendRedirect(/login.jsp?error1); } }三层架构演进Model1时代JSPJavaBean像大杂烩Model2雏形引入Servlet作为控制器现代框架Spring MVC等提供更优雅的实现实际项目中我常用以下包结构组织代码/src /main /java /com /example /controller // 存放Servlet /service // 业务逻辑 /dao // 数据访问 /model // 实体类 /webapp /WEB-INF /views // JSP文件调试技巧在Filter中添加日志记录请求耗时快速定位性能瓶颈public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { long start System.currentTimeMillis(); chain.doFilter(req, res); long end System.currentTimeMillis(); logger.info(请求耗时 (end - start) ms); }6. 过滤器与监听器Web应用的守门人做国际化功能时我通过Filter统一设置编码再也不用在每个Servlet里写request.setCharacterEncoding()了代码整洁度提升明显。编码过滤器实战public class EncodingFilter implements Filter { private String encoding; public void init(FilterConfig config) { this.encoding config.getInitParameter(encoding); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(encoding); res.setCharacterEncoding(encoding); chain.doFilter(req, res); // 必须调用 } }web.xml配置filter filter-nameencodingFilter/filter-name filter-classcom.example.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping监听器典型应用WebListener public class AppListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { // 应用启动时初始化数据库连接池 DataSource ds createDataSource(); sce.getServletContext().setAttribute(dataSource, ds); } public void contextDestroyed(ServletContextEvent sce) { // 应用关闭时释放资源 DataSource ds (DataSource)sce.getServletContext().getAttribute(dataSource); ds.close(); } }开发经验过滤器链的顺序很重要比如应该先执行权限检查再处理编码。在web.xml中filter-mapping的声明顺序决定执行顺序。7. 实战高频考点剖析期末考试中有些知识点就像明星一样频繁出场。根据我帮学弟学妹复习的经验这些是必掌握的重点请求转发 vs 重定向// 请求转发服务器内部跳转 request.getRequestDispatcher(target.jsp).forward(request, response); // 重定向客户端跳转 response.sendRedirect(http://example.com/newpage.jsp);对比总结特性请求转发重定向地址栏变化否是请求次数1次2次数据传递通过request属性通过URL参数或session目标资源必须为同一应用可以跨应用连接池配置参数解析# Druid连接池典型配置 initialSize5 # 初始连接数 maxActive20 # 最大连接数 minIdle5 # 最小空闲连接 maxWait60000 # 获取连接超时时间(ms) timeBetweenEvictionRunsMillis60000 # 检测间隔 minEvictableIdleTimeMillis300000 # 连接空闲时间阈值EL表达式简化操作!-- 传统写法 -- % ((User)session.getAttribute(user)).getName() % !-- EL表达式 -- ${user.name} !-- 处理null值 -- ${empty user ? 未登录 : user.name} !-- 访问Map -- ${scores[math]}JSTL核心标签% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !-- 条件判断 -- c:if test${user.role admin} 管理员菜单 /c:if !-- 循环遍历 -- c:forEach items${products} varprod varStatusstatus ${status.index}. ${prod.name} - ${prod.price} /c:forEach备考建议手写Servlet配置、JDBC完整流程、过滤器链调用顺序等高频考点考试时能节省大量时间。我在考前会默写三遍Servlet生命周期方法确保不丢分。
JavaWeb期末通关指南——从Servlet到MVC的实战复习路线
发布时间:2026/7/15 2:34:40
1. Servlet生命周期从出生到退休的完整旅程第一次接触Servlet时我完全被它的生命周期搞晕了。直到把Tomcat比作餐厅Servlet比作厨师才恍然大悟——init()是厨师上岗培训service()是日常做菜destroy()是退休离职。这个类比让我瞬间理解了Servlet的工作机制。Servlet的生命周期严格遵循三个阶段初始化阶段当Tomcat第一次收到某个Servlet的请求时会调用init()方法。这个方法在整个生命周期中只执行一次就像厨师的入职培训。我常在这里加载配置文件或建立数据库连接public void init(ServletConfig config) throws ServletException { // 读取web.xml中的初始化参数 String dbUrl config.getInitParameter(database_url); // 建立数据库连接池 connectionPool new ConnectionPool(dbUrl); }服务阶段每次请求都会触发service()方法。Tomcat会创建新的request和response对象传入就像顾客点单时给厨师递菜单。这里要注意线程安全问题protected void service(HttpServletRequest req, HttpServletResponse resp) { // 错误示范在方法内定义共享变量 int visitCount 0; // 这个会被多个线程同时修改 // 正确做法使用局部变量或同步块 synchronized(this) { totalVisits; // 线程安全的计数器 } }销毁阶段服务器关闭时调用destroy()方法。这里要优雅地释放资源就像厨师下班前要收拾厨房public void destroy() { // 关闭数据库连接池 connectionPool.close(); // 清理临时文件 tempFileCleaner.cleanUp(); }常见坑点很多同学在init()里初始化耗时资源导致应用启动缓慢。我的经验是使用懒加载第一次请求时再初始化非关键资源。2. JSP九大内置对象你的随身工具包刚开始用JSP时我总在纠结该用request还是session。直到把Web应用比作商场购物才明白request是购物篮一次结账就清空session是会员卡多次消费都有效application是商场公告栏所有顾客可见。核心四大作用域对比对象类比有效范围典型应用场景pageContext收银小票当前页面页面内变量传递request购物篮同一次请求表单提交、请求转发session会员卡同一次会话用户登录状态application商场公告栏整个应用网站访问计数器高频方法实战处理表单时我常用request的这几个方法% // 获取单个参数 String username request.getParameter(user); // 获取复选框多个值 String[] hobbies request.getParameterValues(hobby); // 设置属性供转发使用 request.setAttribute(welcomeMsg, Hello username); %登录验证时session的典型用法% // 存储用户信息 session.setAttribute(currentUser, userObj); // 设置30分钟超时 session.setMaxInactiveInterval(1800); // 退出时清空session session.invalidate(); %易错点很多同学分不清sendRedirect和forward。记住重定向是两次请求地址栏变session有效转发是一次请求地址栏不变request有效。3. 会话管理Cookie与Session的默契配合有次做购物车功能我完全依赖Session存储结果用户清理Cookie后购物车就空了。这才明白Session依赖名为JSESSIONID的Cookie两者必须配合使用。Cookie实战要点// 创建Cookie Cookie userCookie new Cookie(lastVisit, new Date().toString()); // 设置7天有效期 userCookie.setMaxAge(60 * 60 * 24 * 7); // 设置路径使全站可用 userCookie.setPath(/); // 添加到响应 response.addCookie(userCookie);Session底层原理首次请求时服务器创建Session并生成ID通过Set-Cookie头将JSESSIONID发给浏览器后续请求浏览器自动携带这个Cookie服务器根据ID找到对应的Session对象安全增强技巧// 防止Session固定攻击 request.getSession().invalidate(); // 先使旧session失效 HttpSession newSession request.getSession(true); // 创建新session // 重要操作前验证session新鲜度 long createTime session.getCreationTime(); if(System.currentTimeMillis() - createTime 1000*60*5) { // 超过5分钟的session需要重新登录 }性能优化对于大型应用可以将会话数据存储在Redis等缓存中减轻服务器内存压力。4. JDBC进阶从基础操作到连接池优化早期项目我直接用DriverManager当并发量上来后数据库连接成了瓶颈。改用连接池后性能提升了3倍这让我深刻认识到资源管理的重要性。传统JDBC流程// 1. 注册驱动新版可省略 Class.forName(com.mysql.cj.jdbc.Driver); // 2. 获取连接 Connection conn DriverManager.getConnection( jdbc:mysql://localhost:3306/mydb?useSSLfalse, user, pass); // 3. 创建PreparedStatement String sql SELECT * FROM users WHERE age ?; PreparedStatement stmt conn.prepareStatement(sql); stmt.setInt(1, 18); // 4. 执行查询 ResultSet rs stmt.executeQuery(); // 5. 处理结果 while(rs.next()) { System.out.println(rs.getString(username)); } // 6. 关闭资源倒序关闭 rs.close(); stmt.close(); conn.close(); // 重要不关闭会导致连接泄漏Druid连接池配置!-- web.xml中配置 -- context-param param-namedruid.url/param-name param-valuejdbc:mysql://localhost:3306/mydb/param-value /context-param context-param param-namedruid.maxActive/param-name param-value20/param-value /context-paramPreparedStatement防注入示例// 危险容易被SQL注入 String sql SELECT * FROM users WHERE username name ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); // 安全做法 String sql SELECT * FROM users WHERE username?; PreparedStatement stmt conn.prepareStatement(sql); stmt.setString(1, name); // 自动处理特殊字符性能对比测试显示连接池方案比传统方式QPS提升5倍PreparedStatement比Statement快30%。5. MVC设计模式分层解耦的艺术第一次参与团队项目时所有逻辑都写在JSP里改个按钮颜色都要排查半天。采用MVC分层后前端改动再也不会影响后台逻辑维护效率直线上升。经典MVC组件分工ModelJavaBean负责数据存取和业务逻辑public class UserService { public User login(String name, String pwd) { // 验证逻辑... return user; } }ViewJSP专注页面展示% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:if test${not empty user} 欢迎, ${user.name}! /c:ifControllerServlet处理请求流转protected void doPost(HttpServletRequest req, HttpServletResponse resp) { String name req.getParameter(username); String pwd req.getParameter(password); User user new UserService().login(name, pwd); if(user ! null) { req.getSession().setAttribute(user, user); req.getRequestDispatcher(/welcome.jsp).forward(req, resp); } else { resp.sendRedirect(/login.jsp?error1); } }三层架构演进Model1时代JSPJavaBean像大杂烩Model2雏形引入Servlet作为控制器现代框架Spring MVC等提供更优雅的实现实际项目中我常用以下包结构组织代码/src /main /java /com /example /controller // 存放Servlet /service // 业务逻辑 /dao // 数据访问 /model // 实体类 /webapp /WEB-INF /views // JSP文件调试技巧在Filter中添加日志记录请求耗时快速定位性能瓶颈public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { long start System.currentTimeMillis(); chain.doFilter(req, res); long end System.currentTimeMillis(); logger.info(请求耗时 (end - start) ms); }6. 过滤器与监听器Web应用的守门人做国际化功能时我通过Filter统一设置编码再也不用在每个Servlet里写request.setCharacterEncoding()了代码整洁度提升明显。编码过滤器实战public class EncodingFilter implements Filter { private String encoding; public void init(FilterConfig config) { this.encoding config.getInitParameter(encoding); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(encoding); res.setCharacterEncoding(encoding); chain.doFilter(req, res); // 必须调用 } }web.xml配置filter filter-nameencodingFilter/filter-name filter-classcom.example.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping监听器典型应用WebListener public class AppListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { // 应用启动时初始化数据库连接池 DataSource ds createDataSource(); sce.getServletContext().setAttribute(dataSource, ds); } public void contextDestroyed(ServletContextEvent sce) { // 应用关闭时释放资源 DataSource ds (DataSource)sce.getServletContext().getAttribute(dataSource); ds.close(); } }开发经验过滤器链的顺序很重要比如应该先执行权限检查再处理编码。在web.xml中filter-mapping的声明顺序决定执行顺序。7. 实战高频考点剖析期末考试中有些知识点就像明星一样频繁出场。根据我帮学弟学妹复习的经验这些是必掌握的重点请求转发 vs 重定向// 请求转发服务器内部跳转 request.getRequestDispatcher(target.jsp).forward(request, response); // 重定向客户端跳转 response.sendRedirect(http://example.com/newpage.jsp);对比总结特性请求转发重定向地址栏变化否是请求次数1次2次数据传递通过request属性通过URL参数或session目标资源必须为同一应用可以跨应用连接池配置参数解析# Druid连接池典型配置 initialSize5 # 初始连接数 maxActive20 # 最大连接数 minIdle5 # 最小空闲连接 maxWait60000 # 获取连接超时时间(ms) timeBetweenEvictionRunsMillis60000 # 检测间隔 minEvictableIdleTimeMillis300000 # 连接空闲时间阈值EL表达式简化操作!-- 传统写法 -- % ((User)session.getAttribute(user)).getName() % !-- EL表达式 -- ${user.name} !-- 处理null值 -- ${empty user ? 未登录 : user.name} !-- 访问Map -- ${scores[math]}JSTL核心标签% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !-- 条件判断 -- c:if test${user.role admin} 管理员菜单 /c:if !-- 循环遍历 -- c:forEach items${products} varprod varStatusstatus ${status.index}. ${prod.name} - ${prod.price} /c:forEach备考建议手写Servlet配置、JDBC完整流程、过滤器链调用顺序等高频考点考试时能节省大量时间。我在考前会默写三遍Servlet生命周期方法确保不丢分。