JSP从入门到实战:编译机制、内置对象与常见坑全解析

📅 发布时间:2026/9/16 3:10:41
JSP从入门到实战:编译机制、内置对象与常见坑全解析
手写一个JavaWeb项目绕不开JSP。哪怕现在前后端分离已经成为主流但你去翻开任何一本Java入门教材、任何一个老项目的源码甚至爆火的“黑马JavaWeb笔记”里JSP依然占据大半个篇章。面试的时候也会被问到JSP内置对象工作中面对遗留系统还得会改JSP页面。说白了JSP不是最Cool的技术但它是你理解JavaWeb地基绕不过去的一环。这篇博文我不打算给你念文档而是用我这些年做JavaWeb项目踩过的坑、总结出来的经验把JSP从“能看懂”讲到“能上手”再从“能上手”讲到“能排查问题”。无论你是刚学SSM的初学者还是被分到维护老项目的开发希望能帮你把JSP那点事彻底理顺。1. JSP到底是什么先搞清楚它的定位1.1 JSP的本质一个Servlet但更像一个“带模板的Servlet”初学者最容易迷惑的地方在于JSP到底是页面还是程序答案是它俩都是。JSPJavaServer Pages本质上是一个Servlet。当你把一个.jsp文件部署到Tomcat里Tomcat会自动把它编译成一个Java类这个类继承自HttpJspBase最终继承HttpServlet。所以JSP能够接收请求、处理数据、向浏览器输出HTML完全具备Servlet的能力。但它又比Servlet多了“模板标签”的特性——你可以在html标签中间夹带Java代码也就是脚本片段% %。用一个生活类比来解释Servlet像一个只会做菜的厨师你要吃什么得提前告诉他他做好后端给你而JSP像一份带空格子的菜单服务员把菜名填进去你不用整个重写菜单。所以JSP天生就适合“页面中某些部分是静态的、某些部分是动态的”场景。1.2 为什么JavaWeb应用至今离不开JSP很多人说JSP已死但实际上仍然有大量银行、政务、传统企业的系统跑在JSP上。原因有三个上手门槛低会HTML的人可以直接在页面里写Java代码没有Servlet那种大量的out.print()拼接天然适合快速开发CRUD后台。与Servlet配合默契经典的MVC模式里Servlet负责控制逻辑JSP负责视图展示分工清晰。生态成熟JSTL、EL表达式、各种标签库以及JavaWeb老项目的积累让它在改造成本面前显得不可替代。所以我的建议是前端有React/Vue的基础当然好但如果你要维护老系统、参加传统企业面试、毕业设计做“基于JavaWeb的旅游管理系统”或“JSP学生信息管理系统”JSP这门手艺必须掌握。1.3 JSP文件里到底能写什么静态内容动态内容指令一个JSP文件由三部分组成静态内容HTML、CSS、JavaScript原样输出到浏览器。动态内容Java脚本片段% %、表达式% %、声明%! %。指令% page ... %、% include ... %、% taglib ... %这些不是输出内容而是告诉Tomcat“这个页面该怎么编译”。理解了这三部分你就明白为什么JSP能同时充当“页面”和“程序”。后面我们会通过实操案例把这三者串起来。2. JSP生命周期与编译机制class文件到底存哪2.1 五个阶段翻译、编译、加载、实例化、服务JSP的生命周期比普通Servlet多了一个“翻译”和“编译”阶段整体流程是翻译TranslationTomcat把index.jsp解析成index_jsp.java文件。编译CompilationJava编译器把index_jsp.java编译成index_jsp.class。加载与实例化类加载器加载index_jsp.class并创建实例。调用_jspInit()初始化通常用来加载资源。调用_jspService()处理请求生成响应。这个流程只在第一次访问时发生。如果你修改了JSP文件Tomcat检测到时间戳变化会自动重新翻译、编译无需重启容器。2.2 热点问题JSP编译后的class文件保存在哪里网络热搜里反复出现“jsp编译class文件保存在哪里”这也是很多JavaWeb初学者翻车的地方。我直接把答案说清楚如果你是使用Tomcat启动项目JSP生成的.java和.class文件默认存放于Tomcat安装目录的work/Catalina/localhost/应用上下文目录下。举个例子你有一个名为tourism的Web应用部署在Tomcat中访问了http://localhost:8080/tourism/index.jsp。那么存放目录Tomcat安装目录/work/Catalina/localhost/tourism/生成的Java文件.../org/apache/jsp/index_jsp.java生成的Class文件.../org/apache/jsp/index_jsp.class注意org/apache/jsp这个包名路径是固定的前缀后面跟着你JSP文件在应用里的目录结构。如果index.jsp放在/WEB-INF/views下那么生成文件就是.../org/apache/jsp/WEB_005fINF/views/index_jsp.class部分字符会转义。提示如果你用的是EclipseWTP或IntelliJ IDEA内置的Tomcat启动路径可能指向工作区的.metadata/.plugins/org.eclipse.wst.server.core/...或IDEA的target/tomcat临时目录。但原理一致都是Tomcat的work目录。2.3 如何查看JSP编译后的Java代码在IDEA中配置Tomcat后点击Debug启动项目然后访问任意JSP页面。接着切到IDEA下方的Tomcat Log或直接打开系统文件管理器找到刚才说的work目录就能看到生成的.java文件。打开它你会看到类似这样的结构public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { // ... public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { // ... out.write(html...); } }这里就印证了前面说的JSP里的静态HTML全部变成了out.write()调用嵌入的Java代码会被原样放进_jspService()方法中。如果你在页面里写了语法错误通过查看翻译后的Java文件能非常精准地定位是哪一行出了问题。实操时有个小技巧如果你怀疑某个变量作用域不对、查不到out.print出的内容直接看生成的_jspService()方法比反复刷新页面、猜测错误原因高效得多。3. 从零搭建一个JSP项目IDEA创建、Tomcat部署、完整案例3.1 环境准备与版本选择我推荐的基础环境如下大家尽量保持一致避免版本不匹配的坑组件建议版本说明JDK8 或 11老项目推荐JDK8新入门也可用JDK11Tomcat9.0.xJakarta EE规范对应Servlet 4.0兼容JSP 2.3IDEA2020.3以上Ultimate版支持JavaWeb与Tomcat集成Maven3.6可选推荐使用管理依赖MySQL5.7 / 8.0用于完整案例注意Tomcat 10.1之后包名从javax.servlet改成了jakarta.servlet老教程尤其黑马笔记的内容直接搬上去会报错。想跟着老笔记学习最好用Tomcat 9。3.2 IDEA中创建JSP文件并配置TomcatIDEA创建JSP文件的操作很简单但很多人第一次会找半天。正确路径是在项目源码目录比如src/main/webapp上右键。选择New-JSP/JSPX- 输入名称index.jsp。如果New菜单里没有JSP说明你创建的工程类型不对需要先添加Web模块右键项目 -Add Framework Support- 勾选Web Application。创建完成后接着配置Tomcat点击顶部工具栏的Add Configuration...。点号选择Tomcat Server-Local。在Application server中选择本机安装的Tomcat目录不是bin目录是顶层目录。切换到Deployment标签点-Artifact...选择xxx:war exploded开发推荐war exploded热部署效果好。Application context填/或/项目名建议填/访问时直接写localhost:8080/index.jsp减少路径混淆。启动Tomcat后访问http://localhost:8080/index.jsp看到页面就说明整套环境跑通了。3.3 实战一个带MySQL的旅游管理系统后台页面光说不练假把式。这里我写一个极简但能跑的“旅游景点信息管理”的小例子覆盖JSP的指令、脚本、JDBC查询、EL表达式等核心用法。第一步创建数据库与表CREATE DATABASE IF NOT EXISTS travel DEFAULT CHARACTER SET utf8mb4; USE travel; CREATE TABLE tb_spot ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, location VARCHAR(100), price DECIMAL(10,2), intro TEXT ); INSERT INTO tb_spot (name, location, price, intro) VALUES (西湖, 浙江杭州, 0.00, 免费湖景四季皆宜), (故宫, 北京, 60.00, 明清宫殿建筑群), (张家界, 湖南, 225.00, 石峰林立云雾缭绕);第二步编写JDBC工具类在src/main/java/com/example/util/DBUtil.java中package com.example.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection( jdbc:mysql://localhost:3306/travel?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8, root, 你的密码); } }第三步编写JSP页面展示数据在webapp/list.jsp中% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title旅游景点列表/title /head body h1热门景点管理/h1 table border1 tr th编号/th th名称/th th位置/th th价格/th th简介/th th操作/th /tr % // 这里演示在JSP中直接使用Java代码连接数据库 // 真实项目建议通过Servlet或Service层获取数据 java.sql.Connection conn com.example.util.DBUtil.getConnection(); java.sql.Statement stmt conn.createStatement(); java.sql.ResultSet rs stmt.executeQuery(SELECT * FROM tb_spot); request.setAttribute(spotList, rs); % c:forEach items${requestScope.spotList} varrow tr td${row.id}/td td${row.name}/td td${row.location}/td td${row.price}/td td${row.intro}/td tda href#编辑/a | a href#删除/a/td /tr /c:forEach /table % rs.close(); stmt.close(); conn.close(); % /body /html注意上面的代码只是为了演示直接在JSP里操作数据库在真实工程里是大忌。正确的做法是建立Servlet Service DAO三层架构JSP只做展示。第四步测试运行启动Tomcat后访问http://localhost:8080/list.jsp你会看到数据库中的三条记录以表格形式渲染在页面上。这里就完整展示了JSP从创建、编译、执行、渲染到销毁的全过程。3.4 JSP完整案例与经典的“学生信息管理系统”如果你需要更复杂的参考“基于javaweb的JSP学生信息管理系统”是经典的练手项目。它通常包含登录/注销功能学生信息的增删改查分页查询模糊搜索这种系统最适合采用MVC思想StudentServlet接收请求StudentDAO操作MySQLstudentList.jsp渲染页面。我的建议是不要直接用网上现成代码而是拿到思路后自己写一遍。仿写的好处在于你能熟悉从页面表单提交到Servlet接收再回到JSP渲染这条完整链路而这条链路正是JavaWeb面试的核心考点。4. JSP内置对象、EL表达式与页面跳转那些坑4.1 九个内置对象不用声明直接用每个JSP页面在编译时都会自动创建这些对象你直接使用它们就行。我用一张表帮你快速记忆对象类型作用范围典型使用requestHttpServletRequest一次请求获取参数、Request域属性responseHttpServletResponse一次响应设置响应头、重定向sessionHttpSession一个会话保存登录状态、购物车applicationServletContext整个应用全局计数、全局配置pageContextPageContext当前页面管理其他域对象outJspWriter当前响应向页面输出内容configServletConfig当前页面获取初始化参数pageObject当前页面极少使用exceptionThrowable错误页显示异常信息这里特别强调request和session的区别request只在一次请求内有效比如从Servlet请求转发到JSP用request.setAttribute()传数据而session在整个浏览器会话期间有效适合保存用户登录信息。很多新手把大对象塞进session结果内存飙高、并发出错都是没理解作用域。4.2 EL表达式让JSP页面不再堆代码EL表达式${}的作用是取代那些丑陋的% %脚本输出。比如上面例子中的${row.name}如果不用EL你得写成% ((java.sql.ResultSet)request.getAttribute(spotList)).getString(name) %又长又容易出错。EL的最基本用法是访问对象属性${user.name}等价于((User)request.getAttribute(user)).getName()${sessionScope.user.name}显式指定从session作用域取${empty cart}判断对象是否为null或空集合我建议在JSP页面中尽量用EL JSTL把Java脚本降到最少。这样页面干净可维护性高也不容易出现语法错误。4.3 屏蔽JSP离开页面提示一个高频搜索答案热搜词里有一条“屏蔽jsp离开页面提示”我猜很多人遇到的是浏览器弹出“确定要离开此页面吗”的提示框。这个提示通常不是因为JSP本身而是页面用JavaScript注册了beforeunload事件window.addEventListener(beforeunload, function (e) { e.preventDefault(); e.returnValue ; });只要某个页面加了这段代码当你刷新、关闭或跳转时浏览器就会弹出“离开此页面”的确认框。要屏蔽它有两个思路移除事件监听如果不想要这个提示直接不要添加上述监听器。如果代码是第三方插件添加的你可以强制移除window.onbeforeunload null; // 或者 window.removeEventListener(beforeunload, handler);但注意removeEventListener需要拿到原函数引用比较麻烦所以最靠谱的方式是不要注册。设置JSP响应头有些旧系统通过response.setHeader(Refresh, 3;urlxxx)实现自动跳转这属于服务端刷新不受beforeunload影响。但如果你想屏蔽所有离开提示可以检查页面上是否有弹窗相关的JS代码一并删掉。实战经验我在维护一个二手交易平台时遇到订单页每次跳转都弹离开提示排查后发现是某个统计埋点里的beforeunload代码干的。最终在全局JS文件中加了一个条件判断只在外部链接触发时才提示表单内跳转直接放行完美解决。4.4 JSP选择文件夹前端目录上传的坑另一个热搜“jsp选择文件夹”指的不是JSP本身而是如何在网页中实现“选择整个文件夹”的能力。HTML5提供了一个很好用的属性input typefile webkitdirectory multiple在JSP页面中可以这样写form actionUploadServlet methodpost enctypemultipart/form-data input typefile namefolder webkitdirectory multiple button typesubmit上传文件夹/button /form使用webkitdirectory属性后用户点击选择时只能选择文件夹浏览器会把文件夹内所有文件帮你列出来上传。后端Servlet用Part接口接收即可。注意这个属性目前主流浏览器基本支持但老版本某些单位的IE内核浏览器不支持如果一定要兼容IE只能改用Flash上传组件或插件。5. 常见问题排查与避坑手册5.1 “JSP文件不显示”或“500错误”的排查思路现象1页面空白状态码200检查JSP里是否出现了out.flush()之后又导入了大量数据。有可能你的页面很大前端渲染需要时间。检查静态资源路径是否加了${pageContext.request.contextPath}。如果路径写死为/css/style.css当项目上下文不是/时就会404。现象2HTTP Status 500 - Unable to compile class for JSP最常见原因是JSP脚本中使用了已经移除的JDK类比如JavaEE相关包在Tomcat 9中不支持。或者你的JDK版本和Tomcat版本不匹配Tomcat 9要求JDK8Tomcat 10要求JDK11且包名不同。查看日志时直接去看work目录下对应JSP的.java文件错误行号通常非常明确。现象3修改了JSP文件但没有生效确认浏览器缓存。按CtrlF5强制刷新。确认Tomcat是否开启了热部署。手动部署war包时需要把context reloadable设为true或者重启Tomcat后清除work缓存再访问。5.2 JSP里的中文乱码怎么处理乱码是JavaWeb老生常谈的问题我直接给出统一解决方案JSP页面顶部% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 %Servlet接收请求时request.setCharacterEncoding(UTF-8);必须在获取任何参数之前设置否则无效。数据库连接URL追加useUnicodetruecharacterEncodingutf8。MySQL表结构统一使用utf8mb4字符集。按照这四步排查99%的乱码都能解决。剩下1%可能是IDEA控制台编码需要设置Settings - File Encodings全局为UTF-8。5.3 黑马JavaWeb笔记的数据到底怎么配合JSP用“黑马javaweb笔记数据”这个热搜说明很多人在自学时拿到一套笔记和代码却不知道如何把数据跑起来。大部分黑马笔记是基于MySQL并提供sql脚本。拿到脚本后先在Navicat或命令行执行创建库和表然后修改db.properties中的数据库账号密码最后部署项目到Tomcat即可。这里提醒一点个别旧版黑马笔记使用JDBC工具类连接的是com.mysql.jdbc.Driver而这个类在MySQL Connector/J 8.0以上已经被删除。解决方法是把驱动改为com.mysql.cj.jdbc.Driver并把依赖升级到8.0。要是偷懒不想改代码也可以用MySQL 5.7的驱动jar包但最好还是及时升级避免安全问题。5.4 什么情况下应该弃用JSP脚本我见过太多后台管理系统JSP页面里写了上千行Java代码逻辑混乱、SQL直接拼用户输入一旦爆出SQL注入漏洞整个系统都要重构。我的建议是页面中不出现% %脚本只保留${}EL表达式和JSTL标签。Java代码全部放到Servlet/Service/DAO。前端使用AJAX异步请求后台返回JSON由JavaScript渲染页面——这其实是“前后端分离”的雏形也是JSP技术演进的方向。如果你从学第一天就养成这个习惯后续转型Vue或React都会非常轻松。6. 实操心得我在项目里最常用的JSP调试与优化技巧最后再分享几个我平时会用的JSP调试技巧尤其适合刚接手老项目的同学。第一个技巧开启动态页面时间戳输出在JSP头部加一行页面生成时间% new java.util.Date() %这能帮你判断当前看到的是不是缓存页面。如果时间不变说明浏览器或Tomcat缓存了旧版本。第二个技巧善用% include %与jsp:include的区别% include fileheader.jsp %是静态包含在编译阶段就把页面内容复制进来对应一个生成类。jsp:include pageheader.jsp /是动态包含运行时单独访问每次处理请求时都要单独编译执行。如果公共部分不涉及请求数据用静态包含性能更好如果公共部分依赖当前请求的属性必须用动态包含。第三个技巧查看JSP编译生成类时注意过滤日志里的警告当你在work目录找class文件时可能会看到很多_jspx_destroy等陌生方法。别慌这些都由Tomcat自动生成你只需要关注_jspService方法。第四个技巧用异常页面统一处理错误在web.xml中配置error-page exception-typejava.lang.Exception/exception-type location/error.jsp/location /error-page这样当JSP抛出异常时系统会自动跳转到error.jsp避免用户看到堆栈日志。JSP的技术栈并不难难的是把整个JavaWeb链路串起来。我从一开始实习接触第一个基于JSP的旅游网站到今天看别人写的新框架始终认为理解JSP的编译机制和内置对象就是理解Tomcat和JavaWeb的一把钥匙。无论日后技术栈怎么换这层底子永远不会过时。如果你能照着文中的案例亲手敲一遍再顺手去看看Tomcat的work目录顺便把自己项目的公共头尾抽出来用include组合一下相信我你对JSP的掌握程度会超过身边一大半同事。