
简介面向刚开始学习 Java Web 的开发者这份压缩包以一个小而完整的 MiniWebSystem 网站为例把 Servlet、JSP、表单提交、请求转发、页面跳转以及商品信息管理等核心知识点串成一条完整链路。由于示例体量小却能覆盖从用户注册、登录到商品添加和商品展示的典型场景非常适合初学者在较短时间内理解一个 Java Web 项目到底是怎么跑起来的。整个压缩包采用 RAR 格式共五十六个文件大小约六点三五 MB其中包含十八个 Java 源码文件、十八个编译后的 class 文件、五个 JSP 页面、四个 jar 依赖库、两个 XML 配置还有 project、classpath、component、mymetadata 等工程与开发环境描述文件源代码与编译结果同时保留导入集成开发环境后即可对照学习。目前已有 1386 人下载学习学习时不妨从注册、登录、商品新增、商品展示这几个主要页面入手跟随代码查看请求进入服务端后的处理过程再观察 JSP 如何输出结果以及相关配置项在项目里的实际作用从而在较短时间内形成自己的 Java Web 编码思路具备独立完成简单网站开发的能力。 刚学完Java基础准备踏入Web开发大门的同学十有八九都会卡在Servlet和JSP这一步。网上关于这两个技术点的教程并不少但大多是零散的片段要么只讲一个登录页面要么就是一堆纯粹的概念翻译看完之后你依然不知道一个完整的项目到底是怎么组织起来的。今天这篇文章我会用三个可以直接跑起来的实战例子——个人信息展示页、MP4视频播放、数据导出Excel把Java Web开发里最核心的Servlet JSP组合讲透并且全程采用web.xml方式来注册Servlet正好是面试里常问、老项目里常写的经典方式。这套内容我前前后后整理了很长时间挑选的都是初学者阶段最常遇到的业务场景代码不花哨但每一步都有完整的原理说明和踩坑记录。不管你是刚配置好Tomcat、准备跑第一个Servlet的新手还是已经会写一点JSP但搞不清楚数据流转的进阶者这套例子都能帮你把请求→Servlet→JSP→浏览器这条主线打通。1. 先想清楚一件事JSP到底是不是HTML加Java很多初学者在学习JSP的时候都会产生一个非常自然的误解既然JSP页面里可以直接写Java代码用%%那我把所有逻辑都塞进JSP不就行了反正页面也是它逻辑也是它多方便。这个想法在写一个50行的Demo时确实没什么问题但一旦你的页面超过几百行业务逻辑变得复杂这种写法就会迅速变成一场灾难。我的建议是从一开始就建立一个清晰的分工意识Servlet负责接收请求、处理业务、准备数据JSP只负责展示数据。这个原则在Java Web开发里被称为单一职责实践中你会发现遵循这个原则的项目代码的维护成本会低非常多。在下面要讲的三个例子里我刻意保持了这种模式——Servlet是一个纯粹的控制器和数据处理者JSP页面里不写任何复杂的业务逻辑只通过EL表达式${}和JSTL标签来读取Servlet放到request里或session里的数据。这样写的好处是每个文件都有自己的明确用途出了问题也容易定位而且对于初学者来说更容易理解数据是怎么流转的。顺便多说一句我一直觉得JSP页面里直接写大量Java脚本片段scriptlet是一个很不好的习惯尤其是那些夹杂着循环、判断、数据库查询的页面后期改起来是真的痛苦。你想象一下一个页面里HTML、CSS、JavaScript、Java代码四种语言混在一起缩进风格完全混乱光是找到一对匹配的大括号都要找半天更别提别人接手你代码时的心情了。2. 环境准备把第一行Servlet跑起来工欲善其事必先利其器。在开始写代码之前先把开发环境准备好。这里我强烈推荐所有初学者用最经典的组合JDK 8以上版本 Eclipse IDE for Enterprise Java Developers或IntelliJ IDEA Ultimate版 Tomcat 9。IDE用社区版也能凑合但企业级开发还是建议用完整版方便调试Web项目。2.1 一个标准的Web项目目录结构创建Java Web项目后默认的目录结构大致是这样的这里一定要记熟因为后面的所有文件都要放在对应的位置src/ // Java源代码目录 └── com/demo/servlet/ // 存放Servlet类 WebContent/ // Web应用的根目录有些IDE叫webapp或WebRoot ├── WEB-INF/ │ ├── web.xml // 部署描述文件核心配置文件 │ └── lib/ // 存放项目依赖的Jar包比如MySQL驱动POI等 ├── css/ ├── js/ └── jsp文件或页面文件 // 直接放在根目录或按功能分文件夹特别注意WEB-INF目录下的内容浏览器无法直接访问这是Servlet规范规定的安全机制。所以你的JSP页面如果放在WEB-INF里就必须通过Servlet转发forward来访问不能直接输入URL访问这可以保护一些敏感的页面比如后台管理页面。2.2 配置web.xml并注册第一个Servlet创建好项目后把Tomcat配置进去然后我们来修改WEB-INF目录下的web.xml文件注册我们的第一个Servlet。web.xml的核心作用就是告诉Tomcat当用户访问某个URL的时候应该交给哪个类来处理。在Servlet 3.0之前所有Servlet都必须通过web.xml来注册那时候没有注解这种便捷的方式。现在虽然可以用WebServlet注解简化配置但很多老项目依然沿用web.xml方式面试时也经常被问到所以这个基本功必须掌握。下面给出一个完整的web.xml配置示例里面注册了一个名为InfoServlet的Servlet它负责处理info这个路径的请求?xml version1.0 encodingUTF-8? web-app xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlnshttp://xmlns.jcp.org/xml/ns/javaee xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 display-nameJavaWebDemo/display-name !-- 注册Servlet先给Servlet起一个名字并指定对应的Java类 -- servlet servlet-nameInfoServlet/servlet-name servlet-classcom.demo.servlet.InfoServlet/servlet-class /servlet !-- 配置Servlet的访问映射用户访问/Info由InfoServlet处理 -- servlet-mapping servlet-nameInfoServlet/servlet-name url-pattern/Info/url-pattern /servlet-mapping !-- 配置欢迎页 -- welcome-file-list welcome-fileindex.html/welcome-file welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app这里有一个非常关键的点servlet标签和servlet-mapping标签的servlet-name必须完全一致否则Tomcat会报错。很多初学者第一次运行失败就是写了两个不一样的名字。你可以把servlet理解为给这个类办个身份证servlet-mapping理解为告诉Tomcat门牌号对应哪个人两者必须匹配才能找到人。2.3 编写第一个Servlet类接着在src目录下创建包com.demo.servlet新建一个名为InfoServlet的类继承HttpServlet重写doGet方法。当你在浏览器地址栏输入http://localhost:8080/你的项目名/Info的时候浏览器发送的就是一个GET请求这个请求会经过Tomcat的匹配最终执行info.doGet中的代码。package com.demo.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 个人信息展示Servlet * 作用接收请求处理数据转发到JSP展示 */ public class InfoServlet extends HttpServlet { private static final long serialVersionUID 1L; Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 解决中文乱码设置请求和响应的编码格式 request.setCharacterEncoding(UTF-8); response.setContentType(text/html; charsetUTF-8); // 准备数据放到request域对象中 request.setAttribute(name, 张小明); request.setAttribute(age, 22); request.setAttribute(hobby, Java编程、阅读、摄影); // 转发到JSP页面进行展示注意/info.jsp前面的斜杠代表项目根路径 request.getRequestDispatcher(/info.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 写接口时一般建议doPost也调用doGet这样两种请求方式都能处理 doGet(request, response); } }这段代码里有几个地方需要特别留意。第一request.setAttribute()方法是在请求域中存放数据这个数据只在一次请求内有效你重定向redirect之后再取是取不到的但转发forward可以取到因为我们全程从Servlet到JSP是同一个request对象。第二request.getRequestDispatcher().forward()是转发转发过程中浏览器地址栏不会变页面内容是JSP渲染后的结果。3. 三个JSP实战例子从入门到熟练JSPJava Server Pages本质上是一个运行在服务端的Java类它最终会被编译成Servlet来执行。JSP页面中可以写HTML标签也可以通过特殊的标签和表达式来动态输出内容。下面我们来实际操作三个初学者接触最多、也最实用的场景。3.1 个人信息展示页面搞懂request域和EL表达式JSP页面是展示数据的好地方但怎么把Java数据在页面里优雅地展示出来却是有讲究的。很多老教程会让你在JSP里写%request.getAttribute(name)%这种Java脚本片段虽然能实现效果但会让页面看起来非常乱。推荐使用EL表达式简洁且天然支持空值处理。在WebContent目录下新建info.jsp文件代码如下% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% !DOCTYPE html html head meta charsetUTF-8 title个人信息展示/title style /* 这里用一个简单的样式让页面看起来舒服一点 */ .info-card { width: 400px; margin: 100px auto; border: 1px solid #ddd; border-radius: 8px; padding: 30px; box-shadow: 2px 2px 10px rgba(0,0,0,0.1); } .info-card h2 { text-align: center; } .info-row { margin: 15px 0; font-size: 16px; } .info-label { font-weight: bold; display: inline-block; width: 80px; } /style /head body div classinfo-card h2个人名片/h2 hr div classinfo-row span classinfo-label姓名/span !-- EL表达式从request域中取出名为name的值 -- span${name}/span /div div classinfo-row span classinfo-label年龄/span span${age}/span /div div classinfo-row span classinfo-label爱好/span span${hobby}/span /div /div /body /html你在浏览器中访问http://localhost:8080/项目名/info.jsp的话是看不到这些数据的因为没有任何人在这个路径上调用request.setAttribute。你必须先访问http://localhost:8080/项目名/Info让Servlet先运行把数据放进request域再转发到info.jsp。这个流程一定要想清楚JSP本身不产生数据数据一定是从Servlet传递过来的。有的同学可能会问那如果只是做一个静态的个人信息页面不用Servlet直接改把数据写死在JSP里不行吗当然可以但那就没有学习价值了。我们的目标是理解动态数据从哪来为以后对接数据库、从数据库查出数据再展示到页面打好基础。3.2 JSP实现MP4视频播放千万别踩response.getWriter这个坑视频播放是很多初学者都会遇到的问题。有人以为在JSP里写一个video标签就能播放但实际操作中往往会遇到视频黑屏或者只有声音没图像有的甚至直接在页面上显示出一堆乱码。我们先说正解。在WebContent目录下新建video.jsp核心代码如下% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% !DOCTYPE html html head meta charsetUTF-8 titleMP4视频播放页面/title /head body h2视频播放示例/h2 !-- controls属性显示播放控件 autoplay属性自动播放部分浏览器为了用户体验会限制自动播放可以不加 -- video width640 height360 controls source srcvideo/sample.mp4 typevideo/mp4 您的浏览器不支持HTML5视频播放请更换浏览器重试。 /video /body /html这个例子看起来很简单核心是src属性的路径。你必须确认video文件夹确实放在WebContent或webapp目录下并且sample.mp4文件存在才可能播放成功。浏览器访问路径是http://localhost:8080/项目名/video/sample.mp4你可以先直接在浏览器输入这个URL测试一下文件能不能正常打开。那视频播放和Servlet有什么关系呢假设你的视频文件不是放在项目里而是存到了服务器以外的目录比如D盘某个文件夹或者你希望对视频的访问权限做控制只有登录用户才能看就必须通过Servlet来动态读取文件并输出。这时候很多初学者会写类似的代码// 错误示例千万别这么干 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { FileInputStream fis new FileInputStream(D:/video/sample.mp4); byte[] data new byte[fis.available()]; fis.read(data); response.getWriter().write(new String(data)); // 错误 }这个写法会迎来灾难性的后果response.getWriter()是字符输出流只能输出文本如果用它输出二进制文件比如MP4会发生编码转换视频文件会被破坏。正确的方法是使用response.getOutputStream()这个字节输出流并且在输出之前设置响应头告知浏览器这是视频流。这个坑非常经典面试时也经常被挖坑提问。正确写法package com.demo.servlet; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class VideoServlet extends HttpServlet { private static final long serialVersionUID 1L; Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 实际开发中文件路径一般配置在配置文件中这里为了演示写死 String videoPath D:/video/sample.mp4; FileInputStream fis new FileInputStream(videoPath); // 设置响应头告知浏览器内容类型是视频 response.setContentType(video/mp4); // 获取字节输出流 OutputStream os response.getOutputStream(); byte[] buffer new byte[1024]; int len 0; while ((len fis.read(buffer)) 0) { os.write(buffer, 0, len); } fis.close(); os.close(); } }然后在web.xml中注册这个Servlet映射路径为/videoPlayer。之后你把上面的video.jsp里的srcvideo/sample.mp4改成srcvideoPlayer访问video.jsp时浏览器会请求这个ServletServlet读取D盘下的视频文件并输出。这样就实现了视频在外网无法直接访问但通过Servlet代理读取的效果。3.3 用JSPPOI实现数据导出Excel数据导出Excel是很多后台管理系统必备的功能初学者学会了这一招找工作面试时会很有底气。实现思路是在Servlet中生成Excel文件设置好响应头让浏览器自动下载。我推荐使用Apache POI这个开源库它操作Excel很方便。首先下载poi相关的jar包poi-5.x.jar、poi-ooxml-5.x.jar以及它的依赖commons-io等放到WEB-INF/lib目录下。如果你用的是Maven项目直接在pom.xml中添加依赖就行。然后新建一个ExportExcelServletpackage com.demo.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; public class ExportExcelServlet extends HttpServlet { private static final long serialVersionUID 1L; Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 创建一个Excel工作簿这里使用.xls格式兼容性最好 Workbook workbook new HSSFWorkbook(); Sheet sheet workbook.createSheet(用户数据); // 创建表头行第0行 Row headerRow sheet.createRow(0); headerRow.createCell(0).setCellValue(姓名); headerRow.createCell(1).setCellValue(年龄); headerRow.createCell(2).setCellValue(城市); // 模拟几条数据实际开发中这里是从数据库查询的 String[][] data { {张小明, 22, 北京}, {李小红, 25, 上海}, {王大力, 28, 广州} }; // 填充数据行 for (int i 0; i data.length; i) { Row row sheet.createRow(i 1); row.createCell(0).setCellValue(data[i][0]); row.createCell(1).setCellValue(data[i][1]); row.createCell(2).setCellValue(data[i][2]); } // 关键设置响应头告知浏览器这是一个需要下载的文件 response.setContentType(application/vnd.ms-excel; charsetUTF-8); response.setHeader(Content-Disposition, attachment; filename java.net.URLEncoder.encode(用户数据.xls, UTF-8)); // 将工作簿写入响应流 workbook.write(response.getOutputStream()); workbook.close(); } }配置好web.xml后在浏览器中访问该Servlet的映射路径就能自动下载一个Excel文件。注意response.setHeader(Content-Disposition)这段attachment表示附件触发下载filename指定下载的文件名务必用URLEncoder.encode处理中文文件名否则会乱码。4. 常见问题与排查技巧实录这部分是我最想分享的因为初学者踩过的坑来来回回就是那几个我这几年见过太多同学卡在一个特别基础的问题上半天出不来。提前把这些坑踩平你能省下很多时间。4.1 JSP改了不生效问题可能不在代码上很多同学会遇到一个令人崩溃的情况明明修改了JSP文件并保存了刷新浏览器之后页面还是老样子。这时候请按顺序排查下面的几个原因。最常被忽视的就是浏览器缓存。浏览器在访问JSP页面时如果服务器返回的响应头中包含缓存相关设置浏览器可能直接从本地缓存加载页面而不去服务器获取最新内容。你可以强制刷新CtrlF5或者用无痕模式测试来排除这个因素。第二个原因就是项目没有重新发布。在Eclipse中修改JSP文件后如果你是直接运行在Tomcat上有时候Tomcat可能没有热部署成功需要手动重新发布项目或者在Tomcat的Server配置里开启Automatically publish when resources change选项。还有一个很容易被新手忽略的点JSP最终会被编译成Servlet类。当JSP文件修改后Tomcat需要重新编译它。如果修改后的JSP存在语法错误Tomcat会保留上一次成功编译的结果页面看起来当然不生效了——实际上是编译失败了你需要查看Tomcat的日志在控制台查看Catalina或localhost的日志输出来定位具体的语法错误。4.2 连接数据库报错ClassNotFoundException如果你在Servlet中通过JDBC连接MySQL经常会遇到java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。这个错误99%的原因是MySQL驱动Jar包没有被放置到正确的位置。一个非常经典的误区是初学者把mysql-connector-java.jar直接放到了项目的某个死角位置比如放到了src目录下以为这样就可以。正确的位置是在WEB-INF/lib目录下或者如果你的IDE是Maven项目还需要确保依赖已经下载到了Maven本地仓库并且被正确引入到了WAR包中。你可以这样验证打开Tomcat的webapps目录找到你发布出来的项目检查WEB-INF/lib目录下有没有那个驱动Jar包。没有的话一切运行时都找不到驱动就不奇怪了。4.3 中文乱码问题一次性说清楚中文乱码在Java Web开发中太常见了根源只有一个服务器和浏览器之间没有约定好用同一种编码方式来解析数据。涉及到乱码的位置主要有三个请求乱码、响应乱码、数据库乱码。请求乱码当浏览器通过POST提交中文数据时如果Servlet没有设置request.setCharacterEncoding(UTF-8)Tomcat默认用ISO-8859-1解码中文就会变成乱码。如果是GET请求Tomcat的URI编码默认是UTF-8但开发工具或浏览器可能不按这个来最稳妥的方式是在Tomcat的server.xml中给Connector配置URIEncodingUTF-8。响应乱码在Servlet往浏览器输出中文时需要设置response.setContentType(text/html; charsetUTF-8)同时页面本身的pageEncoding也必须是UTF-8这三者要一致缺一个就可能乱码。数据库乱码你需要在数据库连接URL上加上参数?useUnicodetruecharacterEncodingUTF-8并且数据库表本身的字符集也要是utf8。这样数据才能完整走完浏览器→Servlet→数据库这条链路。4.4 HTTP 404、500错误分别意味着什么404代表页面或资源找不到你要排查URL是否写对了项目名称是否正确Servlet的映射路径是否和web.xml中的url-pattern一致请求的页面是否确实存在还有服务器有没有启动成功这是新手最容易忽略却实际影响最大的因素Tomcat界面一闪而过、启动日志报端口被占用的例子我见过太多了。5. 进阶实操心得从能跑到会思考把这几个例子跑通之后你会发现Java Web开发其实没那么玄乎。但我想分享一个关键的个人经验不要只满足于能跑要去想为什么这样能跑。比如写一个用户列表页面当你能熟练地完成Servlet查询数据库存到request域→JSP用JSTL遍历输出表格这条链路后你可以继续问自己几个问题如果数据量很大分页怎么实现如果多个页面需要相同的校验逻辑怎么复用如果项目很大Servlet类越来越多怎么办这些问题的答案最终会引导你走向MVC框架的世界——Spring MVC、MyBatis等框架本质上就是对Servlet做了封装把通用的重复工作参数封装、对象转换、事务管理交给了框架但底层核心还是Servlet那套生命周期和请求处理模型。实践建议手动把上面的代码敲一遍不要复制粘贴。遇到错误先自己看报错信息尝试从Tomcat日志中找答案。修改代码后用浏览器开发者工具F12查看Network面板观察请求的URL、状态码、响应体这能让你对每次请求有一个肉眼可见的直观理解。这套基本功打实了后面接Spring Boot、写微服务都会轻松很多因为你会更清楚框架帮我做了什么而不是仅仅会用某个注解。本文还有配套的精品资源点击获取