
JSP这门技术说实话已经不那么“潮”了但在高校课程和初级岗位里它依然是绕不开的入门关卡。最近在社区里被问最多的一批JSP项目需求翻来覆去就是这几个个人信息展示页面、页面里嵌MP4视频、把数据导出成Excel外加一个让新手原地崩溃的玄学问题——JSP改了不生效。这篇文章我会以一套完整的JSP入门实战项目为载体把这几个需求逐个拆开讲清楚并给出实际验证过的代码、配置和排查清单。适合正在做课程设计、或者刚接触Java Web想练手的人参考。我会按真实开发流程走先搭环境再做页面再搞功能最后聊排查保证你跟着顺一遍之后能独立接手大多数类似的JSP作业。1. 项目全景一个JSP入门实战项目到底该做成什么样1.1 为什么现在还在用JSP做实战JSP全称是Java Server Pages本质上是一个运行在服务端的模板引擎。你可能会问现在前后端分离那么流行为什么课程设计里还是到处是JSP最直接的原因是它能用最小的成本把“前端页面”和“Java后台”串起来浏览器请求一个.jsp文件Tomcat先把JSP翻译成Servlet再编译执行最终返回HTML给浏览器。整个过程不需要单独建前端工程也不需要写复杂的接口一个项目就能跑通数据的展示和提交。我用这类项目带过很多新人最大的体感是JSP能帮你把“请求是怎么来的、响应是怎么去的”这件事讲清楚。它的内置对象——request、response、session、application、out几乎覆盖了Web开发最初级的所有核心概念。把这些对象用熟了后面再去学Spring MVC、学过Filter、学Session管理都会顺很多。所以哪怕到2025年JSP依然是我觉得最适合做Web入门训练的技术之一。1.2 四个热搜需求背后的真实场景这次围绕JSP的热搜词其实是新手最常碰到的四类作业/需求jsp个人信息展示页面用来练页面布局、表单提交、数据接收和回显是最基础的Demo。jsp实现mp4视频播放练的是文件映射、MIME类型、静态资源访问甚至涉及文件流读写。jsp实现数据导出为excel练的是文件下载、IO流、表格生成还要处理中文文件名。jsp改了不生效不是一个功能而是几乎所有JSP新手都会踩的坑属于环境排查类技能。把这四个点放在一个项目里就是一个非常完整的JSP入门实战案例先有个静态页面再把页面做动态接着加媒体播放和文件导出功能最后学会快速定位改代码不生效的毛病。这篇文章的正文就按这条主线来展开。2. 环境搭建与工程结构先别急着写代码2.1 最省心的组合Tomcat JSP ServletJSP项目最常见的运行环境组合是 Eclipse/IDEA Tomcat 8.5/9 JDK 8/11。我推荐用Tomcat 9因为它对Servlet 4.0支持好配置又不像Tomcat 10那样把包名从javax改成jakarta对新手更友好。如果你用的是IDEA新建项目时直接选择“Java Enterprise”勾选Web Application和Servlet依赖IDEA会自动生成web目录如果你用的是EclipseDynamic Web Project也是同样的逻辑。环境变量方面重点是确认以下三点JDK版本别太高Tomcat 9搭配JDK 8或11最稳JDK 17也能跑但某些老版本Tomcat会有兼容问题。配置好CATALINA_HOME路径里尽量不要有中文和空格。如果你不想装完整Tomcat也可以直接把Tomcat配置到IDEA的Application Server里IDEA启动时会自动调用catalina.bat run。我用过一个取巧的办法是下载Tomcat解压版然后不改任何环境变量直接在IDEA里指定Tomcat路径。这样最干净出了问题也能很快判断是不是全局变量污染导致的。2.2 标准目录结构长什么样新建一个标准的Web项目后你会看到类似这样的结构src/main/java // Java源码存放Servlet、Filter、JavaBean等 src/main/webapp // Web根目录 ├─ WEB-INF // 受保护目录外部不能直接访问 │ ├─ web.xml // Web应用描述文件配置Servlet、Filter等 │ ├─ classes // 编译后的class文件一般由IDE生成 │ └─ lib // 项目依赖的jar包如果用Maven则可能没有 ├─ index.jsp // 默认首页 ├─ css/ // 样式文件 ├─ js/ // 脚本文件 └─ video/ // 视频资源目录自定义很多初学者会把JSP直接扔到WEB-INF里面发现浏览器访问不了。这里要记住WEB-INF目录是受保护目录浏览器没法直接通过URL访问里面的JSP。一般有两种方式让JSP可以被访问放在webapp根目录或其子目录下URL直接对应相对路径放在WEB-INF目录下再用Servlet转发或重定向访问这也是MVC模式里JSP作为视图的常见做法。入门阶段我更推荐先把JSP放在webapp根目录下因为开发调试简单URL路径一眼就能看出来。但如果你是想做一个规范一点的项目我会建议把JSP放到WEB-INF/views下配合Servlet转发这样页面不能绕过控制器直接访问安全性和可维护性都更好。2.3 用内置对象理解JSP的运行方式JSP最神奇的地方就是它给你准备了一堆可以直接用的内置对象不需要声明就能在页面里操作。对入门来说记住这六个就够了request封装了浏览器发来的请求可以取参数、取请求头设置请求属性。response封装了将要返回给浏览器的响应可以设置状态码、响应头、重定向。session跟用户会话绑定用来保存登录状态、购物车等数据。application整个Web应用共享的数据区域所有用户都能访问。out向页面输出内容的“老式”工具JSP里拼接HTML常用。pageContext能访问其他内置对象的上下文对象用起来更灵活。我在入门阶段会让新人做一个小实验新建一个test.jsp把下面这些代码放进去% application.setAttribute(appName, JSP入门实战); session.setAttribute(userName, admin); request.setAttribute(msg, hello); % appName: % application.getAttribute(appName) %br/ userName: % session.getAttribute(userName) %br/ msg: % request.getAttribute(msg) %br/这里你会发现request.setAttribute的数据只能在同一个请求内通过转发访问到刷新或直接在新标签页打开就会丢失session的数据只要会话不过期浏览器不关就一直能取到application的数据所有用户都能取到重启Tomcat才会清除。这个实验做一遍你就能明白不同作用域的区别也是后面做登录、做计数器的底层逻辑。3. 个人信息展示页面JSP的经典练手场景3.1 页面结构拆解与数据来源规划“个人信息展示页面”听着简单实际要做好需要规划三部分展示什么、数据从哪来、怎么交互。展示的数据通常包括姓名、学号/工号、头像、岗位/专业、联系方式、个人简介等数据来源可以分成三类写死的静态数据适合纯页面练习。用户通过表单提交后回显的数据适合练接收参数和处理乱码。从数据库查询出来的数据这就涉及JDBC或MyBatis适合综合课程设计。我在入门项目里会建议先做成“静态展示表单提交回显”等你把页面和参数流转捋顺了再加班里的数据库。这样做的好处是一旦页面出问题你可以立刻判断是不是数据源的问题而不是一堆东西混在一起到处找bug。3.2 JSP脚本元素与JSTL标签如何选JSP页面里写Java代码有三种原始方式脚本片段% %、表达式% %、声明%! %。我以前写过一个很真实的页面顶部用%!声明了一个全局方法中间用%循环拼接表格结果其他人维护的时候直接崩溃——因为Java代码和HTML混在一起可读性太差稍微复杂一点的逻辑都会变成“面条代码”。如果只是入门阶段纯脚本不是不能用但我强烈建议养成好习惯页面里尽量只做展示复杂逻辑放到Servlet遍历数据用JSTL标签。这样页面干净报错也好定位。用JSTL要先在web.xml或者页面里引入核心标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %比如展示一个用户列表用JSTLEL这样写c:forEach items${userList} varuser tr td${user.name}/td td${user.email}/td /tr /c:forEach如果你发现EL表达式取不到值第一件事不是改标签而是去检查Servlet往request里放属性时用的key是否和页面上写的一致。这个问题我在带新人时遇到不下十次。3.3 表单提交、数据校验与回显个人信息页面最常见的交互是编辑表单后提交然后页面显示更新后的信息。我先给一个简单的版本用JSP本身来处理请求% page contentTypetext/html;charsetUTF-8 languagejava % % request.setCharacterEncoding(UTF-8); String name ; String email ; if (POST.equalsIgnoreCase(request.getMethod())) { name request.getParameter(name); email request.getParameter(email); } % html body form methodpost 姓名input typetext namename value% name %/br/ 邮箱input typetext nameemail value% email %/br/ input typesubmit value保存/ /form p当前姓名% name %/p p当前邮箱% email %/p /body /html这里有个关键点接收请求参数之前先调用request.setCharacterEncoding(UTF-8)否则POST提交的中文很容易变成乱码。如果想彻底一点最好在项目的web.xml里配置一个CharacterEncodingFilter后面第7章我会详细说三道防线的写法。实际项目中表单提交后通常还要做非空校验。入门阶段可以简单用if (name null || name.trim().isEmpty())但更推荐写到Servlet里做JS只做前端提示后端再做一次校验这样数据安全性才靠谱。4. MP4视频播放JSP页面里播放视频的三个方案4.1 方案一HTML5 video标签直接引用最简单的方式是把MP4文件放到webapp/video/目录下然后在JSP里写一个video标签video controls width640 src${pageContext.request.contextPath}/video/intro.mp4 您的浏览器不支持HTML5视频播放。 /video这个方案适合文件不大、无权限要求的内部演示比如课程设计的介绍视频。需要注意两点MP4的编码格式最好是H.264因为浏览器的兼容性最好如果文件超过几十MB建议先放进静态目录测试一次确认能正常读取再集成到项目里不然你很难分清是网络问题还是代码问题。使用这种方式的另一个隐含坑是路径问题。很多新手喜欢直接写srcvideo/intro.mp4但当前页面如果经过Servlet转发到/views/detail.jsp相对路径就会错。最稳妥的做法是用${pageContext.request.contextPath}拼绝对上下文路径。4.2 方案二Servlet做文件流式输出如果你的视频文件不想放在Web根目录或者想做访问控制那就需要用Servlet输出文件流。先写一个VideoServlet映射到/video/play然后从请求参数里拿文件名再通过文件流写回响应。最基础版本WebServlet(/video/play) public class VideoServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String fileName request.getParameter(file); String basePath D:/media/; File videoFile new File(basePath, fileName); response.setContentType(video/mp4); response.setHeader(Content-Length, String.valueOf(videoFile.length())); try (FileInputStream in new FileInputStream(videoFile); OutputStream out response.getOutputStream()) { byte[] buffer new byte[4096]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } } } }HTML里的video src指向video controls src${pageContext.request.contextPath}/video/play?fileintro.mp4/video这个版本能播但拖动进度条时会发现经常“拉不动”。原因是没有处理HTTP的Range请求。浏览器在播放视频时默认会发起带Range: bytes0-的请求服务端如果直接返回200而不是206浏览器就会放弃拖拽。要做到真正的流式播放代码至少要支持返回206状态码并且解析Range头。核心逻辑是long start 0; long end videoFile.length() - 1; String range request.getHeader(Range); if (range ! null range.startsWith(bytes)) { String[] rangeArr range.substring(6).split(-); start Long.parseLong(rangeArr[0]); if (rangeArr.length 1 !rangeArr[1].isEmpty()) { end Long.parseLong(rangeArr[1]); } } response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206 response.setHeader(Content-Range, bytes start - end / videoFile.length());我实际测试下来不处理Range只能应付“点开就播完”的小文件一旦视频有20MB以上用户拖进度条就会卡住。如果你课程设计里恰好要求“视频可以拖拽进度”一定别用最简单的流输出糊弄把Range处理补上这个点往往就是答辩的加分项。4.3 方案三鉴权播放与防盗链思路很多项目要求必须登录后才能看视频这时就不能直接暴露静态资源路径否则别人复制URL就能绕过登录。我的做法是在Servlet里判断Session中是否存在登录用户如果没有直接返回403或者重定向到登录页。HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 请先登录); return; }再进一步可以给播放URL加一个时间戳token比如/video/play?fileintro.mp4tokenxxxxtoken用服务器端密钥和时间戳生成服务端每次校验token是否过期和签名是否正确。这个方案能有效防止别人拿到视频地址后到处传。不过作为入门项目一般做到Session校验就够了。5. 导出ExcelJSP实现数据导出的两种写法5.1 方式一POI生成真正的xlsx文件要导出真正的Excel文件最常见的方式是用Apache POI。在Maven项目里引入依赖dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version /dependency然后写一个ExportExcelServlet示例代码try (Workbook workbook new XSSFWorkbook()) { Sheet sheet workbook.createSheet(用户列表); Row header sheet.createRow(0); header.createCell(0).setCellValue(姓名); header.createCell(1).setCellValue(邮箱); int rowIndex 1; for (User user : userList) { Row row sheet.createRow(rowIndex); row.createCell(0).setCellValue(user.getName()); row.createCell(1).setCellValue(user.getEmail()); } response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); String fileName java.net.URLEncoder.encode(用户列表.xlsx, UTF-8); response.setHeader(Content-Disposition, attachment; filename\ fileName \); workbook.write(response.getOutputStream()); }这里要注意两点文件名如果直接写成中文Excel下载后很容易乱码或直接变成下划线URLEncoder.encode是稳妥的做法不过编码后浏览器下载时一般会解码成正确中文。另外XSSFWorkbook适合生成.xlsx文件数据量几万行以内问题不大如果你用.xls后缀就得用HSSFWorkbook它最多只能支持65535行超了会报错。5.2 方式二纯JSP输出CSV应急方案有时候你手里只有一个纯JSP项目没有Maven也没有POI的jar包临时又要一个“能用Excel打开”的导出功能。这时候可以把数据输出成CSV格式因为CSV本质是纯文本Excel能直接双击打开。示例% page contentTypetext/csv;charsetUTF-8 % % page importjava.util.* % %-- 模拟数据实际可以从数据库查询 --% % response.setHeader(Content-Disposition, attachment; filename\users.csv\); % 姓名,邮箱 张三,zhangsanexample.com 李四,lisiexample.com这段代码输出的内容浏览器会自动下载成一个users.csv文件用Excel打开就能看到表格。但这个方案有一个大坑CSV默认用逗号分隔如果字段内容里本身包含逗号、换行或引号Excel解析就会错位。解决办法是给每个字段都用双引号包起来内容中的双引号再转义成两个双引号实现起来就是写一个简单的escapeCsv()方法。顺带说一句很多老师其实能接受你导出的文件打开后变成CSV表格因为本质上Excel能打开功能就算完成。但如果课程设计明确写了“导出.xlsx格式”那就老老实实用POI。5.3 导出文件名乱码与空数据问题导出文件最常见的两个问题一是中文名乱码二是数据为空时Excel打不开。文件名乱码我在上面用URLEncoder解决了一次但这里还要提醒不同浏览器的解码行为不完全一样。在老版本IE里URLEncoder编码后是号服务器端需要用转成空格现代浏览器一般没问题。稳妥的写法是分浏览器判断但对课程设计来说用URLEncoder已经足够。数据为空时很多人会遇到导出的Excel文件打开报错原因是代码里只创建了Workbook但没创建任何Sheet或者有Sheet但没行POI生成的空文件某些Excel版本不认。解决方法是至少创建一个空Sheet或者导出一个“无数据”提示行。我的习惯是总是先创建Sheet再检查数据集合大小如果为空就写一个“暂无数据”的标题行至少保证文件可读。6. JSP改了不生效的秘密排查清单6.1 缓存浏览器、服务器、编译产物三层“明明改了JSP文件刷新页面还是老样子”是每个JSP初学者都会问的问题。大多数时候问题不是出在代码上而是缓存在作怪。至少有三层缓存需要依次排查浏览器缓存浏览器可能会缓存整张页面。如果能看见地址栏URL没错但内容一直是旧版按CtrlF5强制刷新试试。Tomcat对JSP的检测机制Tomcat默认会检测JSP文件是否更新但检测周期不是即时的尤其在开发模式下也有一两秒延迟。如果改了以后稍微等一下再刷新往往就好了。编译产物缓存JSP第一次被访问时Tomcat会把JSP翻译成Java源码再编译成class文件。如果你看到项目工作目录下work/Catalina/localhost/yourWebApp/org/apache/jsp里的.java和.class文件没更新说明Tomcat可能没有重新编译。6.2 Tomcat热部署配置的正确姿势有些场景下JSP改了不生效是因为IDE设置的“热部署”根本没起作用。IDEA里启动Tomcat时有个“On frame deactivation”下拉框建议选“Update classes and resources”这样你切回浏览器时IDE会自动把JSP和class同步到Tomcat的部署目录。Eclipse里则是在Server视图里勾选“Automatically publish when resources change”。如果你用的不是IDE而是直接改Tomcatwebapps目录下的文件那么需要确认Tomcat是不是以可重载方式运行。最直接的办法是打开conf/context.xml在Context标签上设置Context reloadabletrue这样Tomcat检测到webapps下的class或JSP有变化会自动重新加载Web应用。但它也有个副作用每次改动都会触发整个应用重启耗费时间。生产环境不建议开本地开发无所谓。6.3 我的一次实战排查记录我印象很深的一次是带学生调一个课设项目他反复说“JSP改了没用”。我检查了一圈最后发现他改的文件在src/main/resources目录下而JSP文件本身在src/main/webapp下面。他改错地方了刷新一万遍也不可能生效。后来我整理了这样一张排查表给新人看特别有用现象可能原因验证方法处理方式刷新页面还是旧内容浏览器缓存CtrlF5强制刷新无JSP内容没变但页面显示最新数据Tomcat没重新编译看work目录下的编译时间重启Tomcat或设置热部署改动Java类/配置不生效IDE没自动发布看部署目录文件时间换成Update classes and resources改了文件但访问的还是404文件路径不对看URL是否匹配用页面路径拼接调试修改了web.xml没生效Tomcat未重载Web应用看启动日志手动重启应用这个表我一直推荐学生贴在电脑旁边几乎能解决90%的“不生效”问题。7. 从入门到上手的几条经验7.1 中文乱码的三道防线JSP中文乱码是另一个高频问题解决起来要按三层来处理。第一层是文件编码JSP文件本身要保存成UTF-8并且页面头部写清楚% page contentTypetext/html;charsetUTF-8 languagejava %第二层是请求编码POST请求参数乱码时在Servlet或JSP里执行request.setCharacterEncoding(UTF-8)如果是GET请求乱码还需要在Tomcat的server.xml里给Connector加上URIEncodingUTF-8。第三层是响应编码确保response的ContentType包含charsetUTF-8。一个更彻底的做法是在项目里写一个Filter对每个请求先执行request.setCharacterEncoding(UTF-8)再执行response.setCharacterEncoding(UTF-8)。这样后面写页面和处理逻辑时就不用每个地方都重复设置。7.2 404、500等常见报错速查JSP开发里报错常见但并不可怕。404表示“页面或资源找不到”先看URL有没有拼错、文件是否真的在部署目录下500表示“服务端程序出了问题”一般需要看Tomcat日志。我整理几个最常遇见的报错信息原因快速解决HTTP Status 404 – Not Found路径不对 / 资源不存在检查URL和项目部署名HTTP Status 500 – Unable to compile classJSP里的Java代码错误看详细堆栈检查变量类型ClassNotFoundException: com.mysql.jdbc.DriverJDBC驱动没加入lib引入mysql-connector-java的jar包The superclass “javax.servlet.http.HttpServlet” was not found项目没有关联Tomcat运行环境在IDEA中配置Tomcat依赖Port 8080 was already in use端口被占用换端口或结束占用进程端口占用是每个人都会遇到的Bug。Windows下可以用netstat -ano | findstr 8080找到PID再在任务管理器结束进程不想结束别人的服务也可以直接改Tomcat端口IDEA里在Run Configuration的VM options或server.xml里改都行。7.3 JSP与Servlet的职责边界最后聊一点方向性的东西。很多人做完JSP项目习惯把数据库查询、业务判断、HTML输出全塞到一个JSP页面里运行是能跑但代码维护起来非常痛苦。我的建议是尽早养成“Servlet负责控制和逻辑JSP负责展示”的习惯Servlet接收请求、调用JavaBean或DAO、把结果放到request或session里再request.getRequestDispatcher(/xxx.jsp).forward(request, response)到JSP页面展示。这样做的最大好处是出错后定位快页面排版错就在JSP里查业务逻辑错就在Servlet里查。项目一复杂这种分层的优势会越来越明显。以后你学Spring MVC的时候会发现Controller就是这个Servlet的升级版JSP就相当于View层理念完全一致。最后分享一个小习惯我每次改完JSP都会顺手看一眼部署目录里的文件时间再强制刷新页面最后才查日志。这套动作看似麻烦但能帮你把大量时间省在真正的问题上。JSP入门没那么玄把页面、请求、响应这三条线理清楚后面学任何Java Web框架都会快很多。