尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Java Web请求全链路拆解:从Cookie会话到Servlet与数据库分页
简介面向Java Web学习者的个人微博应用项目围绕会员管理、个人中心、消息发布等核心模块系统展示Web原理与前后端开发全流程适合课程设计、毕业设计及Servlet/JSP初学者参考是一次完整的Web应用实践。压缩包内共71个文件以22个Java源码、24个class编译文件、5个JSP页面为主辅以XML配置、CSS样式、Jar依赖及数据库文件等整体仅3.33MB包含Eclipse工程配置文件导入后即可运行调试。目前已有223人学习。借助完整的工程目录读者可对照学习用户注册登录、数据持久化、接口设计、实时推送等核心知识还可参考代码中的权限管理、单元测试与搜索思路是一份帮助串联Web应用前后端技术的实用案例。1. 前端选 Java Web 方向后第一次把浏览器请求打到后端时最先撞上的不是代码 bug而是一个反直觉的事实HTTP 本身完全“健忘”你刚注册完账号刷新一次页面却发现时间线里出现了别人发的微博而且页面右上角还挂着你自己的头像。浏览器明明只是往服务器发了一个普通的 GET 请求服务器是怎么在“没有记忆”的 HTTP 协议上认出你、知道你关注了谁、该给你推什么内容的答案不是某个魔法字段而是 Cookie、Session、数据库查询、Servlet 调度这四件事串成的一条完整链路。搞不懂这条链路前端写的是“页面死不死”后端写的则是“登录态丢不丢、数据乱不乱、连接池崩不崩”。个人微博应用恰好是把这些机制全部走一遍的最小完整案例注册登录涉及会话管理发微博涉及数据写入时间线涉及查询与排序异步刷新涉及 JSON 序列化。把这套机制拆开你会看到一个请求从浏览器到数据库再回到浏览器的完整旅程——这也是为什么值得专门写一篇文章把它讲透。2. 一次微博刷新背后从 TCP 连接、线程池到 Servlet 的完整调度链2.1 先看清请求的“底层路径”不是 url 直连 Java 方法很多初学者以为浏览器地址栏里输入 index.jsp服务器就会“直接执行那个文件”。实际上请求要先经过一层网络协议栈、一个监听端口、一个线程池最后才由 Servlet 容器交到你的代码手里。我一般会这么理解Servlet 容器例如 Tomcat启动后会在某个端口上持续 accept TCP 连接。浏览器发来的 GET /index.jsp HTTP/1.1 请求是一段纯文本容器把这段文本解析成请求行、请求头、请求体封装成一个 HttpServletRequest 对象然后从线程池里捞出一个空闲线程在这个线程上执行匹配到的组件代码。这里有个隐蔽但关键的点容器处理请求的工作线程是复用的。你写的代码里如果让线程 sleep或者在一个请求里反复创建数据库连接然后不关消耗的不是“这一个请求”而是整个应用的吞吐能力。后面避坑环节里连接耗尽的问题根源就在这里。2.2 URL 映射与 Filter 链谁先碰这个请求请求进入容器后会先经过一层 Filter 链再到达 Servlet。理解先后顺序很重要Filter 的执行发生在 Servlet 的 service 方法之前因此编码设置、登录校验这类横切逻辑通常会放在 Filter 里而不是在每个 Servlet 里重复写。一个典型个人微博项目的 web.xml 或注解配置里会声明类似这样的映射WebFilter(urlPatterns {/api/*, /index.jsp}) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); chain.doFilter(req, resp); } }chain.doFilter 是关键它表示把请求继续向后传递。如果忘了调用这一行请求就会在这里被“吞掉”后面的 Servlet 永远执行不到前端表现就是接口一直 pending。再往下容器根据请求路径找到对应的 Servlet。对于 /api/login 这个路径容器会在映射关系里找到 LoginServlet然后调用它的 service 方法service 再根据 HTTP 方法分发到 doGet 或 doPost。所以 doPost 不是“你写的入口”而是整个调度链上最靠近业务逻辑的那一层。2.3 Servlet 三兄弟的生命周期init、service、destroyServlet 不是“每个请求新建一个实例”恰恰相反同一个 Servlet 在容器里通常只有一个实例被所有请求线程共享。这是新人最容易踩的并发思维误区。init 方法容器启动或首次请求时执行一次适合做资源初始化。service 方法每个请求都会执行但它内部按方法类型分发给 doGet/doPost。destroy 方法容器关闭时执行一次适合释放资源。这里就出现一个并发隐患如果在一个 Servlet 里定义了成员变量并用它存某个登录用户的身份信息那么用户 A 的请求和用户 B 的请求会同时读写同一个变量。正确做法是把每个请求、每个用户特有的数据放进 request 作用域、session 作用域或方法局部变量而不是 Servlet 成员变量。一个真实场景的登录 Servlet 大致长这样WebServlet(/api/login) public class LoginServlet extends HttpServlet { private UserService userService new UserServiceImpl(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userService.login(username, password); if (user ! null) { HttpSession session req.getSession(true); session.setAttribute(currentUserId, user.getId()); resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(loginError, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }注意这里的两个跳转方式差别非常微妙sendRedirect 是让浏览器再发一次新请求地址栏会变成 /index.jspforward 是服务器内部直接改渲染目标地址栏保持 /api/login 不变。登录失败用 forward能把错误信息通过 request 属性带给登录页登录成功用 redirect能避免用户刷新页面时把“登录提交”再次重放。这段代码里的 UserService 是业务层它的内部会调 DAO 去查数据库这就是后面章节里时间线查询的入口。3. 会话机制Cookie 里的 JSESSIONID、服务端 Map 与“假装记住你”3.1 HttpSession 到底存在哪上一章登录成功时调用 req.getSession(true)这里发生了两件事服务器创建或找到一条会话记录同时在响应头里写一个 Set-Cookie。会话记录本身是一个服务端的内存对象本质是一个键值结构存着这个会话的创建时间、最后访问时间、失效时间以及你往里塞的各种 attribute。Cookie 里存的是会话的唯一标识大多数容器默认叫 JSESSIONID。浏览器收到后后续每次请求都会把这个 Cookie 原样带回。服务器一看 JSESSIONID就能从自己的会话集合里找到对应的 HttpSession 对象然后把“当前登录的是谁”这个信息取出来。丢登录态最常见的原因就是这条链路某一段断了环节常见坑表现Cookie 未设置 HttpOnly前端脚本能读到 JSESSIONIDXSS 后可被窃取会话劫持Cookie 未设置 Path应用上下文路径变化后 Cookie 带不过来登录失效Session 超时超过 maxInactiveInterval 无访问容器自动清理突然要重新登录容器重启内存会话全部消失重启后全部掉线3.2 登录校验 Filter为什么每个受保护页面都要过一道关卡微博应用中发微博、看时间线、改头像这些操作都不允许匿名访问。如果每个 Servlet 都手写一遍“检查 session 里有没有 currentUserId”代码会瞬间失控。标准做法是做一个统一的登录校验 Filter只让登录过的请求通过。WebFilter(urlPatterns {/api/postWeibo, /api/timeline, /index.jsp}) public class LoginCheckFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq (HttpServletRequest) req; HttpSession session httpReq.getSession(false); if (session ! null session.getAttribute(currentUserId) ! null) { chain.doFilter(req, resp); } else { HttpServletResponse httpResp (HttpServletResponse) resp; httpResp.sendRedirect(httpReq.getContextPath() /login.jsp); } } }这里专门用了 getSession(false)如果当前没有会话直接返回 null而不是新造一个。很多人写成 getSession(true)导致未登录用户每次访问受保护页面都会被服务器创建一个新会话服务器内存里堆满“幽灵会话”。登录校验的严谨程度可以从这一个参数上看出来。3.3 密码不能明文存加盐哈希与登录流程中容易被跳过的那一步个人微博项目里最常见的重灾区是密码存储。有人直接明文存有人用 MD5 存一次就当作加密。MD5 确实“不是明文”但它经不起彩虹表和暴力碰撞。真正常见且可靠的思路是加盐哈希每个用户生成一个随机盐把盐拼到密码后面再进行多次哈希然后盐和哈希结果一起入库。public class PasswordUtil { private static final int ITERATIONS 10000; public static String hashWithSalt(String rawPassword, String salt) throws Exception { byte[] result rawPassword.getBytes(UTF-8); for (int i 0; i ITERATIONS; i) { byte[] combined new byte[result.length salt.getBytes(UTF-8).length]; System.arraycopy(result, 0, combined, 0, result.length); System.arraycopy(salt.getBytes(UTF-8), 0, combined, result.length, salt.length()); result MessageDigest.getInstance(SHA-256).digest(combined); } return bytesToHex(result); } }这里的迭代次数不是随便写的。每次循环都把上一步的摘要结果再和盐一起重新哈希目的是把计算成本抬高到“暴力破解一个密码需要足够长时间”的量级。与此同时登录验证时也要重复同样次数的哈希再比对所以登录接口天然会比其他接口慢一些这是安全换来的可接受代价。从这一章能看到整个登录态的本质服务器并没有“记住你”它只是保存了一条状态记录并通过一个不可伪造的标识和浏览器保持约定。你刷新一次页面看到自己的头像和别人的微博其实是 session 里的 userId 被拿去查了关注关系、查了时间线最终拼出了“好像记得你是谁”的完整体验。4. 时间线是怎么“算”出来的feed 表结构、拉取式查询与分页索引4.1 核心表结构用户表、微博表、关注关系表个人微博应用的数据模型围绕三个核心实体用户、微博内容、关注关系。微博表是业务的主体关注关系决定时间线范围。建表 SQL 里索引设计决定了查询上限。CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, salt VARCHAR(32) NOT NULL, created_at DATETIME NOT NULL ); CREATE TABLE weibo ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, content VARCHAR(280) NOT NULL, created_at DATETIME NOT NULL, INDEX idx_user_time (user_id, created_at DESC), INDEX idx_time (created_at DESC) ); CREATE TABLE follow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, follow_user_id BIGINT NOT NULL, created_at DATETIME NOT NULL, UNIQUE KEY uk_user_follow (user_id, follow_user_id) );这里有两个索引细节值得展开。idx_user_time 支撑“查某个人的所有微博并按时间倒序”这种个人主页查询idx_time 支撑“全站最新微博”这种公共时间线查询。当 create_at 被加上后MySQL 可以直接按索引顺序倒着读避免做文件排序。4.2 拉取式时间线为什么这种小项目不选择“推送”时间线设计有两条路拉取模式和推送模式。拉取模式是当你请求时间线时现场去查你关注的人最近发了什么推送模式是发微博时系统立刻把这条微博复制到所有粉丝的收件箱里。个人微博项目用拉取有两个现实原因。一是用户量小关注关系简单查询开销完全扛得住二是推送模式要维护“每个用户的收件箱表”每次发微博都是一次扇出写入粉丝多时要写入几千条数据还要处理事务一致性问题。拉取模式把开销从“写时”转移到了“读时”代码简单失误率低。你手上这份资源里如果用 JDBC 手写 SQL 查询走的一定是拉取模式因为它不需要引入消息队列或异步任务。4.3 时间线 SQLJOIN 与分页的正确姿势查询时间线时最直观的写法是查出所有关注对象的 id再去微博表里查这些人的微博。但逐条查会产生 N1 次 SQL。正确做法是用子查询加 JOIN 一次性查出完整结果SELECT w.id, w.content, w.created_at, u.username FROM weibo w JOIN user u ON u.id w.user_id WHERE w.user_id IN ( SELECT follow_user_id FROM follow WHERE user_id ? ) ORDER BY w.created_at DESC LIMIT 20 OFFSET 0;这条 SQL 的执行顺序和人们通常以为的不一样不是先执行 IN 子查询再把结果拿去 JOIN而是优化器会尝试先把 follow 表按 user_id 定位再通过索引找对应的 weibo 行。这里占位符 ? 必须用 PreparedStatement 传参不能直接字符串拼接否则一个包含单引号的微博内容就能把整个查询语法打碎这就是 SQL 注入的典型入口。分页参数 LIMIT 20 OFFSET 0 表达的是“每页 20 条跳过 0 条”。页码越大OFFSET 值越大MySQL 需要先把前面所有行全部扫出来再丢弃这就是深翻页变慢的根源。资源 DAO 层如果提供了“上一页最后一条微博 id”这种参数走的其实是键集分页比如 WHERE w.created_at ? ORDER BY w.created_at DESC LIMIT 20它能直接走索引而不需要扫已读过的数据两种分页方式的性能差距在数据量过万后会非常明显。DAO 层把 SQL 查询结果封装成对象的常见思路是先取 ResultSet再逐行构建 Weibo 对象组装成 List 返回给 Service 层Service 层再把时间列表交给 Servlet 响应输出。这一层封装虽然代码量大却是 JDBC 应用的骨架。如果资源里使用了连接池配置那连接的生命周期管理就是下一章要说的重灾区。5. 前端 Java Web 避坑清单乱码、连接耗尽、异步数据拿不到、会话丢失5.1 中文乱码编码设置的位置比想象中更早现象注册时输入“我喜欢写代码”存入数据库变成“?????”页面显示一串问号。原因Servlet 容器解析请求体时默认按 ISO-8859-1 读取字节流。你调用 req.getParameter 的时候参数已经被按错误编码解码了后面无论怎么 new String 转码都只能靠“先还原字节再重解”这步补救而这个还原过程一旦遇到不可逆字符就彻底失败。解决在最先接触请求的 Filter 里于读取任何参数之前执行 req.setCharacterEncoding(UTF-8)同时给 JDBC 连接 URL 加上 characterEncodingUTF-8建表时把字符集设为 utf8mb4。三处缺一不可Filter 少了请求参数报错URL 少了数据库写入报错表字符集少了存储和展示不一致。5.2 数据库连接不释放一次忘记 close 引发的雪崩现象应用刚启动一切正常运行二十分钟后随便一个接口都卡住不动重启后恢复过一段时间又卡死。原因Java 代码里每次用 DriverManager.getConnection 创建连接用完后没有 close。数据库的最大连接数被占满后新的请求只能排队等超时。更隐蔽的是即使写了 close如果出现在 try 块中途抛异常的分支上close 根本执行不到。解决统一使用连接池例如手动实现一个池或直接依赖容器数据源并在 finally 里释放连接。连接的 close 方法在池化场景下不是真正断开数据库连接而是“归还”到池中所以每个 Request 的处理都必须保证归还否则池里的可用连接会越借越少。从 Java 7 开始的 try-with-resources 写法能把释放逻辑收敛到一行代码中是这类资源里最值得优先改造的点。5.3 Ajax 拿不到 JSON返回类型和响应头各错一半现象前端用 fetch 或 XHR 异步请求 /api/timeline控制台报解析错误后端日志没异常MySQL 查询也正常执行。原因Servlet 往 response 里写 JSON 字符串时既没有设置 Content-Type又顺手返回了一个 JSP 页面。浏览器收到的是 text/html 类型把它当网页处理前端却按 JSON 解析自然解析失败。另一个常见原因是在 Servlet 里写了 resp.sendRedirect把异步请求整个重定向到登录页前端拿到的是登录页的 HTML 片段。解决返回 JSON 的接口统一设置 resp.setContentType(application/json;charsetUTF-8)用第三方工具完成对象到 JSON 字符串的序列化同时避免在异步接口里使用重定向。前端 fetch 里也要加上请求头声明并在 then 里判断 res.ok 再 res.json()。两个方向各退半步问题就能定位到具体哪一侧。5.4 Session 突然丢失登录态容器重启和浏览器策略各有一套逻辑现象后端代码一改重启应用服务器所有用户被踢下线用户用无痕窗口打开应用每次都要重新登录。原因HttpSession 默认存在容器内存里不是存在数据库或磁盘文件中。容器一重启内存清空所有 JSESSIONID 对应的会话记录全部消失。无痕窗口则是浏览器不保留会话级 Cookie 的表现因为容器默认没有给 JSESSIONID 设置 Max-AgeCookie 只存活在浏览器进程内存里窗口一关就没了。解决先分清楚这是“预期行为”还是“产品缺陷”。个人微博项目里容器重启导致掉线可以接受因为会话数据量小、重建成本低。如果产品要求“记住我”七天免登录就要用持久化的登录令牌方案登录成功后生成一个随机 token 存数据库把 token 写进带 Max-Age 的 Cookie 里后续请求用 token 换取用户身份。这个方案绕开了 Session 内存存储的边界代价是每次请求多一次数据库查询。5.5 时间线深翻页越来越慢LIMIT/OFFSET 的代价被低估现象前几页的微博秒开翻到三十页后接口耗时从 80ms 涨到 800ms。原因数据库要 LIMIT 20 OFFSET 580就得从最新的那条开始一路数过前六百条再丢掉前五百八十条。翻得越深数据库做的工作越多而且这些被丢掉的行没法走索引加速。解决改用键集分页。前端把当前页最后一条微博的时间或 id 传回后端SQL 改成 WHERE w.created_at ? ORDER BY w.created_at DESC LIMIT 20。这个条件下数据库能直接利用索引定位到指定位置随后顺序读取不管翻到多深扫描范围都固定在一页大小内。注意时间字段在有数据重复时可能出现同一秒内多条记录被跳过的问题稳妥做法是使用 id 作为排序键因为 id 在主键索引里天然单调递增、单调且唯一。6. 从这门课的设计取舍再看现代的 Java Web手写链路的价值在框架封装之后反而更清晰普通 Web 开发者现在写接口习惯是框架一键生成前端发请求后端写一个 controller 方法加几个注解返回 JSON完事。看起来和 Servlet 时代天差地别但对照前几章的链路你会发现 Spring 这类框架做的每一件事都是在用更抽象的方式重新表达 Servlet 规范里已有的机制RequestMapping 替代了 web.xml 里的 URL 映射拦截器替代了 Filter 链JdbcTemplate 替你把 PreparedStatement 的创建、参数绑定、ResultSet 遍历收进了一行代码而控制器方法拿到 HttpSession 参数的过程本质上仍是容器在调用链里解析并注入会话对象。所以我一直觉得先手写一遍 Servlet、Service、DAO 三层再去看框架效率是最高的。你踩过的乱码、连接池耗尽、会话丢失、分页变慢每一个坑对应的都是框架底层帮你封装的某个组件。框架不是魔法它是把你的血泪经验固化成了一批默认配置。遇到复杂的异常时如果脑子里没有这条手写链路就只能对着堆栈瞎猜。验证这套理解最直接的方法打开浏览器开发者工具观察从输入 URL 到页面渲染完成的网络请求时间线找到那个异步加载时间线的请求点开它的请求头找到 Cookie 里的 JSESSIONID再去服务器日志里找到那次请求对应的 SQL 日志你可以亲眼看到 session 里的 userId 被拼进了哪个查询条件。从那以后我每接手一个 Java Web 项目第一件事不是跑起来而是先把它的请求链路图画一遍哪个 Filter 先执行、哪个 Servlet 处理、Session 从哪里写入又在何处读、数据库连接的生命周期覆盖在哪个调用范围内。这张图画清楚了项目里百分之八十的诡异问题都有了定位方向。希望这份从请求到会话再到数据的完整链路拆解能帮你在跑通这份资源时少走几步当年我走过的弯路。本文还有配套的精品资源点击获取
RELATED

相关推荐

Spring Cloud Gateway局部过滤器实战:用GatewayFilterFactory实现灵活权限校验

Spring Cloud Gateway局部过滤器实战:用GatewayFilterFactory实现灵活权限校验

做网关这块时间不短了,前面几篇把Gateway的基础路由、全局过滤器和注册中心接入都捋了一遍。这篇我们专门聊GatewayFilterFactory,也就是局部过滤器,并且拿权限校验这个最常见的业务场景开刀。不是泛泛讲API怎么用,而是从“为什么…

📅 2026/10/23 18:28:22
Kubernetes 1.18.8 源码包构建与调试实战指南

Kubernetes 1.18.8 源码包构建与调试实战指南

简介:本资源为 Kubernetes 1.18.8 版本的离线部署套件,面向容器运维工程师、K8s 初中级学习者及私有化环境部署人员,解决无公网场景下快速搭建高可用 K8s 集群的核心需求。压缩包共21个文件,涵盖4个Shell脚本(init.sh、…

📅 2026/10/23 18:23:20
Kubernetes 1.18.8源码包深度解析与kubelet编译实战

Kubernetes 1.18.8源码包深度解析与kubelet编译实战

简介:本资源为 Kubernetes 1.18.8 版本的离线部署与运维工具集,面向容器平台工程师、K8s 初中级学习者及私有云环境部署人员,解决离线环境下快速搭建、初始化与管理 K8s 集群的核心需求。压缩包共21个文件,涵盖4个Shell脚本&#…

📅 2026/10/23 18:23:20
MORE NEWS

更多资讯

📰

碰碰好评卡核心功能实现逻辑与落地部署实战及常见问题排查指南

在电商用户运营场景中,碰碰好评卡是一种轻量化的用户反馈激励工具,通过权益激励引导消费者提交真实购物评价,帮助商户提升店铺权重与商品转化率。本文从技术视角出发,完整讲解碰碰好评卡的架构设计、核心功能实现、多端部署流程以…

📰

碰碰好评卡功能实现全流程实战开发指南 附核心源码与踩坑总结

背景与价值 围绕关键词「碰碰好评卡」,本文结合CSDN内容发布场景,从行业背景、核心问题、解决方案、实施步骤和常见误区五个维度展开。 知识库参考 抖客 抖音返利3.0 #1:标题:抖客 抖音返利3.0 标签:抖客 抖音返利3.0 …

📰

灵魂无处安放?归属感缺失的底层逻辑与落地方法

1. 这个标题到底在问什么“为什么有人会觉得灵魂无处安放”——这句话乍一看像是一句文艺感慨,但如果把它当成一个真实存在的心理状态来拆解,它其实指向了一个非常具体的体验:一个人明明身处正常的生活轨道里,有工作、有社交、有日…

📰

帕德博恩数据集多模态耦合机制与工况编码解析

简介:本资源是一份面向机械故障诊断、智能运维及工业大数据方向研究者与工程师的帕德博恩轴承数据集深度解读文档,聚焦多模态数据理解与故障分类建模基础。文档系统梳理了K0##(健康)、KA##(外圈)、KB##&…

📰

SpringBoot+Spring Security+Redis后台管理系统部署与登录认证避坑指南

简介:这是一套面向Java后端开发者与计算机专业学生的完整网站后台管理系统源码,基于Spring Boot、MyBatis、Spring MVC、Spring Security与Redis构建,适合希望掌握企业级Web开发技术栈、理解各组件协同工作的中高级学习者。项目以Spring Boot…

📰

IIS迁移选型与避坑:四类Web服务器对比实战

简介:面向需要在 Windows 环境下快速搭建轻量 Web 服务、尤其是调试 ASP 页面的用户,这份小型工具包提供了一套替代 IIS 的简易思路。它围绕可执行服务程序、页面样式表与说明网页组织,适合个人开发者、教学演示或旧项目维护场景,…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬