资讯动态

JavaWeb入门核心链路:Servlet、Tomcat与HTTP协议实战

发布时间:2026/10/17 4:34:46 来源:尧图企业网站定制
1. 为什么JavaWeb入门总在配置地狱里打转刚接触JavaWeb那会儿我最深的印象不是Servlet有多强大而是被各种XML配置、环境变量、依赖冲突折腾到怀疑人生。一个最简单的Hello World页面从装JDK到跑通Tomcat中间能踩出七八个坑。后来带过几个新人发现大家卡住的地方惊人地一致不是逻辑写不出来而是根本不知道一个请求从浏览器发出到页面返回中间到底经过了哪些环节。这篇笔记想做的事情很明确把JavaWeb入门阶段最核心的那条链路讲透。它适合完全没碰过服务端开发、或者学过Java SE但一到Web就懵的人。核心关键词就几个——Servlet、Tomcat、HTTP协议、MVC分层、会话管理。我不会只丢一堆代码让你抄而是把每一步为什么这么做说清楚因为入门阶段最怕的就是照着敲能跑、换个需求就废。先给一个整体认知JavaWeb的本质是让Java程序能够接收HTTP请求、处理业务逻辑、再返回HTTP响应。围绕这个本质衍生出了容器Tomcat、组件Servlet/Filter/Listener、规范Servlet API和框架Spring MVC等。入门阶段先把原生Servlet这条线走通后面学框架就是水到渠成的事。很多人急着上Spring Boot结果连请求参数怎么绑定都说不清这就是跳步的代价。下面我按真实的学习和实操顺序把这条链路拆成几个部分来讲每一部分都配上我实际踩过的坑和验证过的做法。2. 环境搭建JDK、Tomcat与IDE的版本匹配逻辑2.1 版本选择不是越新越好入门阶段最容易犯的错是盲目装最新版本。我见过有人用JDK 21配Tomcat 8结果启动直接报类版本不兼容。原因很简单Tomcat不同版本对JDK的最低要求不一样而且Servlet规范版本也不同。这里给一个我实测稳定的组合参考组件推荐版本说明JDK8 或 11入门阶段兼容性最好绝大多数教程和依赖都支持Tomcat8.5 或 9对应Servlet 3.1/4.0够用且资料多IDE任意主流Java IDE重点看是否支持Web项目部署构建工具Maven统一管理依赖避免手动拷jar选JDK 8不是因为它老而是因为入门阶段你需要的所有API它都有而且网上90%的JavaWeb资料都以它为基准。等你把Servlet生命周期、会话管理这些搞明白了再升级到新版本迁移成本几乎为零。2.2 Tomcat目录结构里藏着的关键信息很多人装完Tomcat就直接启动从来没看过它的目录。其实目录结构本身就说明了容器的运行逻辑bin启动和关闭脚本startup.bat/startup.sh在这里conf配置文件server.xml是核心端口、连接器都在这改webapps默认的应用部署目录把war包或文件夹丢进去就能跑workJSP编译后的临时文件出问题可以清空它logs日志排错第一现场我建议入门时至少打开server.xml看一眼Connector标签把默认的8080端口改成你习惯的端口理解一下port和protocol这两个属性的含义。这一步看似多余但能让你建立容器在监听端口、等待请求的直觉。2.3 第一个Web项目的目录规范用IDE创建Web项目时它会自动生成一套目录结构。核心是web或webapp目录里面必须有WEB-INF。这个目录的名字是规范定死的大小写都不能错。WEB-INF下面通常放web.xml部署描述符配置Servlet映射、过滤器等classes编译后的class文件lib项目依赖的jar包而WEB-INF外面的文件比如HTML、JSP、图片是可以被浏览器直接访问的WEB-INF里面的内容浏览器访问不到只能通过服务端转发。这个内外有别的设计是安全边界后面讲请求转发和重定向时会反复用到。注意如果你手动创建目录一定要确认WEB-INF拼写正确我见过把WEB-INF写成WEB_INF导致整个项目404的情况排查了半天。3. Servlet从一个请求进来到一个响应出去3.1 Servlet的本质与生命周期Servlet说白了就是一个Java类它实现了特定接口能被容器调用。容器Tomcat负责创建它、初始化它、调用它处理请求、最后销毁它。这个创建-初始化-服务-销毁的过程就是生命周期。关键方法有三个public class HelloServlet implements Servlet { Override public void init(ServletConfig config) { // 容器启动或首次访问时调用一次做初始化 } Override public void service(ServletRequest req, ServletResponse res) { // 每次请求都调用核心业务逻辑写这里 } Override public void destroy() { // 容器关闭时调用一次释放资源 } }实际开发中我们不会直接实现Servlet接口而是继承HttpServlet它帮我们把service方法按HTTP方法拆成了doGet、doPost等。这样你只需要重写关心的方法。生命周期里有个容易忽略的点Servlet默认是单例的。容器只创建一个实例多个请求共用一个对象。这意味着你不能把请求相关的数据存成成员变量否则会出现线程安全问题。我早期写过一个计数器把count定义成成员变量结果并发访问时数字乱跳就是这个原因。正确做法是把状态放在方法内的局部变量或者用线程安全的数据结构。3.2 请求参数的接收与中文乱码接收参数最常用的是request.getParameter(name)。但这里有个经典坑POST请求的中文乱码。原因是容器默认用ISO-8859-1解码请求体。解决办法是在读取参数之前设置编码request.setCharacterEncoding(UTF-8);注意这行必须放在第一次getParameter之前否则不生效。GET请求的乱码则跟Tomcat版本有关较新版本默认URI编码已经是UTF-8老版本需要在server.xml的Connector上加URIEncodingUTF-8。我个人的经验是统一在过滤器里设置编码这样所有请求都覆盖到不用每个Servlet都写一遍。这也是后面Filter章节要讲的内容。3.3 请求转发与重定向的选择这是入门阶段最容易混淆的一对概念。两者都能让请求跑到另一个资源但机制完全不同。对比项请求转发forward重定向redirect发起方服务端内部服务端告诉浏览器浏览器地址不变变成新地址请求次数1次2次能否共享request数据能不能目标范围只能本应用内可以跨应用、跨站点转发用request.getRequestDispatcher(/path).forward(request, response)重定向用response.sendRedirect(/path)。选择逻辑很简单如果需要保留请求数据、且目标在本应用内用转发如果是提交表单后防止刷新重复提交PRG模式用重定向。我见过有人登录成功后用转发结果用户刷新页面又提交了一次登录请求这就是没理解两者区别的后果。3.4 Servlet映射的几种写法映射决定了哪个URL由哪个Servlet处理。web.xml里配置是一种方式注解是另一种WebServlet(/hello) public class HelloServlet extends HttpServlet { ... }注解更简洁但要注意如果同时用了注解和web.xml且配置了相同的URL容器启动会报错。入门阶段建议统一用一种别混着来。映射路径支持通配符比如/user/*表示/user下的所有路径都进这个Servlet。这个特性在做权限拦截时很有用但要注意匹配优先级精确匹配 长路径匹配 通配符匹配 默认匹配。4. 会话管理Cookie与Session到底怎么选4.1 HTTP是无状态的这是所有会话问题的根源HTTP协议本身不记录任何请求之间的关系。你第一次请求登录了第二次请求服务器根本不知道你是谁。会话管理就是为了解决这个问题让服务器在多次请求之间记住用户。主流方案有两种Cookie和Session。很多人背过Cookie存客户端、Session存服务端但真到用的时候还是懵。我换个说法Cookie是服务器发给浏览器的一张会员卡浏览器每次请求都带着它Session是服务器自己维护的一个档案柜每个用户对应一个抽屉抽屉编号就是SessionID而这个编号通常通过Cookie传给浏览器。4.2 Cookie的实操细节创建和发送CookieCookie cookie new Cookie(username, zhangsan); cookie.setMaxAge(60 * 60 * 24); // 有效期1天单位秒 cookie.setPath(/); // 整个应用可见 response.addCookie(cookie);读取CookieCookie[] cookies request.getCookies(); if (cookies ! null) { for (Cookie c : cookies) { if (username.equals(c.getName())) { String value c.getValue(); } } }几个坑点setMaxAge不设置的话Cookie只在当前会话有效关浏览器就没了setPath不设置的话默认只在当前路径及子路径可见可能导致其他页面读不到Cookie的值不能直接存中文需要编码。4.3 Session的工作机制与超时Session由容器管理获取和设置属性HttpSession session request.getSession(); session.setAttribute(user, user); Object user session.getAttribute(user);getSession()和getSession(false)的区别很关键前者没有就创建一个后者没有就返回null。在只需要读取、不想无端创建Session的场景比如统计在线人数要用后者。Session超时可以在web.xml里配置session-config session-timeout30/session-timeout /session-config单位是分钟。超时后Session失效用户需要重新登录。这里有个经验不要把Session超时设得太长否则服务端内存压力大也不要太短否则用户操作到一半就掉线。30分钟是多数场景的平衡点。4.4 分布式场景下Session的局限单机环境下Session很好用但一旦部署多台服务器问题就来了用户在A服务器登录Session存在A上下次请求被负载均衡打到B服务器B上没有这个Session用户就掉线了。入门阶段不用深究解决方案但要有这个意识。常见的思路有Session粘滞同一用户固定打到同一台、Session复制服务器之间同步、集中存储把Session放到Redis等外部存储。这些等你真正接触分布式部署时再深入现在知道Session不是银弹就够了。5. Filter与Listener把横切逻辑从业务里抽出来5.1 Filter解决的是什么问题假设你有20个Servlet每个都需要做三件事设置编码、检查登录、记录日志。如果每个Servlet里都写一遍代码重复不说改一处要改20处。Filter就是用来处理这种横切关注点的。Filter在请求到达Servlet之前拦截可以在doFilter里决定是放行还是拦截public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { req.setCharacterEncoding(UTF-8); res.setContentType(text/html;charsetUTF-8); chain.doFilter(req, res); // 放行交给下一个Filter或Servlet } }chain.doFilter之前的代码在请求处理前执行之后的代码在响应返回后执行。这个前后包裹的特性让Filter非常适合做耗时统计、事务管理。5.2 登录拦截的完整实现登录拦截是Filter最经典的应用。思路是判断Session里有没有用户信息有就放行没有就跳转到登录页。HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; String uri request.getRequestURI(); // 放行登录页和静态资源 if (uri.contains(/login) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, res); return; } HttpSession session request.getSession(false); if (session ! null session.getAttribute(user) ! null) { chain.doFilter(req, res); } else { response.sendRedirect(request.getContextPath() /login.jsp); }这里有个细节request.getContextPath()返回的是应用上下文路径。如果你的应用部署路径不是根路径直接写/login.jsp会跳错地方。这个坑我在部署到非根路径时踩过排查了很久才发现是路径问题。5.3 Filter的链式执行顺序多个Filter会组成一条链执行顺序由配置顺序决定。注解方式下顺序不确定取决于类加载顺序所以如果顺序重要建议用web.xml配置filter-mapping的先后就是执行顺序。一个常见的顺序建议编码Filter放最前登录Filter其次日志Filter最后。因为编码必须最先设置登录拦截应该在业务逻辑之前日志记录可以包在最外层统计总耗时。5.4 Listener监听什么Listener用于监听容器事件常用的有三种ServletContextListener应用启动和关闭时触发适合做初始化比如加载配置、建立连接池HttpSessionListenerSession创建和销毁时触发适合统计在线人数ServletRequestListener请求创建和销毁时触发适合做请求级别的资源管理入门阶段最值得掌握的是ServletContextListener因为很多框架的初始化就是靠它。理解了这个后面看框架源码会轻松很多。6. JSP与MVC为什么最终要分层6.1 JSP的本质是ServletJSP看起来像HTML里嵌Java但容器会把它翻译成一个Servlet类然后编译执行。所以JSP能做的事情Servlet都能做只是JSP写页面更方便。JSP里常用的语法% %脚本片段写Java逻辑% %表达式输出值% %指令比如% page contentTypetext/html;charsetUTF-8 %但入门之后你会发现在JSP里写大量Java代码是灾难。页面和逻辑混在一起改个样式可能碰坏逻辑改个逻辑可能碰坏页面。这就是MVC要解决的问题。6.2 MVC分层的职责划分MVC把应用拆成三部分Model模型数据和业务逻辑比如JavaBean、Service、DAOView视图展示层通常是JSP或HTMLController控制器接收请求、调用模型、选择视图通常是Servlet一个典型的请求流程浏览器请求 → ControllerServlet接收 → 调用Service处理业务 → Service调用DAO访问数据库 → 结果封装进Model → Controller把Model存入request → 转发到ViewJSP渲染 → 返回HTML。这样分层之后每层职责单一改页面不影响逻辑改逻辑不影响页面。我强烈建议入门阶段就按这个结构组织代码哪怕项目很小。习惯养成了后面学Spring MVC就是换个写法而已。6.3 EL表达式与JSTL让JSP干净起来ELExpression Language用来在JSP里取值比% %简洁${user.name} ${sessionScope.user.name}JSTL是一套标签库用来做循环、判断等逻辑c:forEach items${userList} varuser trtd${user.name}/td/tr /c:forEach用了EL和JSTL之后JSP里几乎不用写Java代码页面干净很多。这也是MVC落地的重要一环。入门阶段花点时间掌握这两个收益很大。6.4 一个完整的CRUD链路示例以用户列表为例走一遍完整链路浏览器访问/user/listUserListServlet接收请求Servlet调用UserService.list()获取用户列表Service调用UserDao.findAll()查询数据库结果List存入request.setAttribute(userList, list)转发到userList.jspJSP用c:forEach遍历渲染表格这条链路走通一次JavaWeb的核心就掌握了大半。剩下的都是在这个骨架上加东西加过滤器做权限、加监听器做初始化、加连接池优化性能。7. 入门阶段最值得记住的几条实操经验7.1 排错先看日志别瞎猜Tomcat的logs目录下有catalina.out等日志文件异常堆栈都在里面。我见过太多人遇到500错误就到处改代码其实日志里第一行就写明了原因。养成先看日志、再看代码的习惯能省一半时间。7.2 路径问题占新手bug的一半相对路径、绝对路径、上下文路径这三个概念要分清。在JSP里引用资源建议用${pageContext.request.contextPath}拼绝对路径避免因为当前页面路径不同导致404。在Servlet里跳转用request.getContextPath()。这个习惯能帮你避开大量路径坑。7.3 依赖冲突用Maven的依赖树排查如果启动时报ClassNotFoundException或NoSuchMethodError多半是依赖冲突。用mvn dependency:tree看依赖树找到重复的jar用exclusions排除掉旧版本。手动拷jar到lib目录是万恶之源能不用就不用。7.4 别急着上框架我理解大家想快点用上Spring Boot的心情但原生Servlet这条线走通了你对请求怎么进来、怎么出去会有肌肉记忆。到时候学框架你看到的是它帮我封装了什么而不是它到底在干嘛。这个认知差距决定了你是会用框架还是懂框架。7.5 代码分层从第一天就开始哪怕只写一个登录功能也按Controller-Service-Dao分好包。包名可以用controller、service、dao、entity。这不是形式主义而是让你在写代码时自然思考这段逻辑属于哪一层。分层思维是JavaWeb进阶最重要的软实力。8. 从原生Servlet到框架的过渡信号什么时候说明你可以学框架了我的判断标准有三个第一你能不查资料写出一个带登录拦截的完整CRUD第二你能说清楚一个请求从浏览器到数据库再回来的完整链路第三你遇到404、500、乱码这些问题能自己定位。达到这三条你会发现Spring MVC做的事情你都能理解DispatcherServlet就是个总控ServletRequestMapping就是URL映射Controller就是Controller层的封装拦截器就是Filter的升级版。框架没有变魔法它只是把原生Servlet的重复劳动抽象掉了。到那个时候回头看这篇笔记里的每一个概念你会有原来如此的感觉。JavaWeb入门难难在概念多、链路长、坑分散但只要按请求的生命周期把这条线捋顺剩下的就是熟练度问题。我带的几个新人里凡是老老实实把原生Servlet写透的后面学框架都很顺急着跳步的反而在框架里绕更久。这个规律供你参考。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑