ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JSP运行原理深度解析:从翻译编译到Tomcat容器实战

2026/8/4 5:04:47 拓冰建站 浏览量
JSP运行原理深度解析:从翻译编译到Tomcat容器实战 1. JSP概述从历史到现实的再认识如果你在最近几年才开始接触Java Web开发可能会觉得JSPJavaServer Pages是一个有些“古老”甚至“过时”的技术。毕竟现在的主流是Spring Boot、Vue.js、React这些前后端分离的框架。但有趣的是在搜索引擎和各大技术社区里关于JSP的搜索热度依然不低从“JSP中Ajax方法带参数传递”到“VSCode搭建JSP环境”再到各种关于Tomcat配置、Session共享的疑难杂症都说明它依然活跃在许多遗留系统、企业内部应用甚至教学场景中。我见过不少维护着十几年历史系统的工程师每天还在和JSP页面打交道。所以理解JSP不仅仅是理解一段历史更是掌握一把能打开许多现有系统大门的钥匙。它就像Java Web世界的“拉丁文”虽然不再是官方推荐的新项目首选但读懂它你就能理解Servlet、MVC模式乃至后来诸多框架的设计思想源头。简单来说JSP是一种用于创建动态Web内容的技术。它的核心目标是把Java代码嵌入到HTML页面中让开发者能相对方便地生成动态网页而不必像纯Servlet那样用一大堆out.println(“html”)来拼接HTML字符串。你可以把它想象成一个“模板”这个模板最终会被服务器转换并执行为一个Servlet。对于初学者而言JSP降低了动态网页开发的门槛对于有经验的开发者理解其运行原理则是解决性能问题、理解Web容器行为的关键。2. JSP运行原理深度拆解从.jsp文件到客户端响应很多人知道JSP最终会变成Servlet但这个“变”的过程具体是怎样的里面有哪些关键的步骤和容易踩坑的细节我们把它拆开来看。2.1 核心转换过程翻译与编译当你把一个.jsp文件例如index.jsp部署到Tomcat这类Web容器也叫Servlet容器后第一次有客户端请求这个页面时会发生一系列幕后操作。这个过程不是魔法而是有严格规范的。第一步翻译TranslationWeb容器如Tomcat中的JSP引擎通常是Jasper会识别到这个对.jsp资源的请求。引擎不会直接执行.jsp文件而是首先将其“翻译”成一个纯Java源代码文件即一个.java文件。这个文件本质上就是一个Servlet类。例如你的index.jsp可能会被翻译成index_jsp.java并通常存放在Tomcat的工作目录如work/Catalina/localhost/yourApp/org/apache/jsp下。这个翻译过程就是把所有HTML文本转换成out.write()语句把JSP脚本片段% ... %、表达式% ... %、声明%! ... %等按照JSP规范转换成对应的Java代码。注意很多初学者遇到的“ClassNotFoundException”或“JSP文件无法访问”错误根源往往在这里。检查Tomcat的work目录权限、磁盘空间是否充足是排查这类问题的第一步。我遇到过因为磁盘满导致JSP无法生成.java文件从而一直报500错误的情况。第二步编译Compilation翻译生成的.java文件只是一个文本文件需要被编译成JVM可以执行的字节码。JSP引擎会调用JDK或内置编译器将这个.java文件编译成.class文件。至此一个标准的Servlet类就诞生了。这个类继承自HttpJspBaseTomcat中的实现它本身继承自HttpServlet并重写了_jspService方法。你写的所有JSP内容最终都位于这个_jspService方法体内。第三步加载与实例化Loading Instantiation编译好的.class文件会被Web容器的类加载器加载到内存中然后像普通Servlet一样容器会创建它的一个实例。第四步初始化Initialization容器调用该Servlet实例的init()方法进行初始化。对于JSP转换来的Servlet这通常只发生一次。第五步请求处理Request Handling当后续请求到达时容器不再需要重复翻译和编译而是直接调用已实例化的Servlet的_jspService方法。该方法接收HttpServletRequest和HttpServletResponse对象执行其中包含了你原始逻辑的Java代码生成HTML输出并通过响应流发送给客户端浏览器。这个过程解释了为什么第一次访问JSP页面通常比较慢需要经历翻译和编译而后续访问就很快直接执行已编译的类。也解释了当你修改了JSP文件后为什么有时需要重启应用或等待容器检测到文件变化并重新翻译编译。2.2 内置对象揭秘为什么可以直接用request、out在JSP页面的脚本片段里你可以直接使用request、response、session、out、application等对象而不需要自己声明或获取。这常常让初学者感到疑惑。其实这些就是JSP的“内置对象”Implicit Objects。在_jspService方法被生成时JSP引擎会自动声明并初始化这些对象。我们来看一下它们在生成的Servlet类中大概是什么样子public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { // 内置对象实际上是方法参数或局部变量 PageContext pageContext ...; HttpSession session request.getSession(); ServletContext application getServletContext(); ServletConfig config getServletConfig(); JspWriter out pageContext.getOut(); // ... 你的JSP内容被转换成使用这些变量的代码 out.write(html\\r\\n); out.write(body\\r\\n); out.write(Hello, ); out.print( request.getParameter(name) ); // 对应 % request.getParameter(name) % out.write(\\r\\n); out.write(/body\\r\\n); out.write(/html); }所以request就是_jspService方法的参数session是通过request.getSession()获得的out是JspWriter实例它包装了response.getWriter()。理解这一点你就明白了这些对象的生命周期和作用域与它们在Servlet中是完全一致的。例如session对象在会话期间有效application即ServletContext在整个Web应用生命周期有效。实操心得虽然可以直接使用内置对象但在复杂的JSP页面中过度混用Java脚本和HTML会导致代码难以维护这就是所谓的“JSP Model 1”的弊端。更佳实践是使用JSTL标签和EL表达式来替代大多数脚本片段让页面更专注于视图展示。例如用c:forEach替代Java循环用${user.name}替代% user.getName() %。3. JSP与Servlet、Tomcat的三角关系很多人分不清JSP和Servlet也搞不懂Tomcat在这里面扮演什么角色。我们可以把它们看作一个协作体系。Servlet是Java Web应用的基石是一个用于处理HTTP请求和响应的Java类。它遵循“一个类一个URL模式或一组”的映射关系所有的逻辑业务逻辑、控制逻辑、展示逻辑最初都挤在Servlet里。JSP本质上是Servlet的“语法糖”或“特殊形式”主要为了解决Servlet在生成动态HTML时过于繁琐的问题。JSP允许你以更接近HTML的方式编写页面但最终它还是会变成一个Servlet。所以JSP是一种特殊的Servlet专为简化视图层开发而生。Tomcat它是一个Web容器Servlet Container也是JSP容器。它的核心职责是管理Servlet/JSP的生命周期加载、初始化、执行、销毁。提供网络服务监听端口如8080解析HTTP协议。提供JSP引擎如Jasper负责JSP到Servlet的翻译和编译工作。管理会话Session、上下文Context等。所以关系链是这样的你编写.jsp文件 - 部署到Tomcat- Tomcat在需要时将其翻译编译成Servlet类 - Tomcat加载并执行这个Servlet类来处理请求。常见配置问题实录“tongweb jsp is missing from the classpath”这通常出现在一些国产或特定应用服务器如TongWeb中错误提示JSP依赖的库不在类路径下。解决方案是确保Web应用的WEB-INF/lib目录下或服务器的共享库中包含jsp-api.jar和servlet-api.jar或对应版本的依赖。在Maven项目中需要检查provided范围的依赖是否正确配置。“两个Tomcat部署两个相同的项目在浏览器中登录一个时另一个项目的session过期”这涉及到Session管理。默认情况下Session是存储在单个Tomcat实例内存中的。两个独立的Tomcat进程内存不共享。如果你需要Session共享必须借助外部存储如Redis并配置Tomcat的Session管理器如使用RedissonSessionManager。这不是JSP的问题而是分布式环境下的Session一致性问题。版本匹配问题如“jdk和tomcat的版本有要求吗”答案是肯定的。高版本Tomcat通常需要匹配的JDK版本。例如Tomcat 10.x需要JDK 11或更高版本Tomcat 9.x支持JDK 8及以上。不匹配可能导致无法启动或运行时错误。4. JSP核心语法元素与最佳实践解析虽然现在不鼓励在JSP中写大量Java代码但了解其基本语法对于阅读和维护旧代码至关重要。更重要的是理解如何正确使用它们。4.1 脚本元素谨慎使用的“利器”脚本片段Scriptlet% Java代码 %这是最强大的也是最容易被滥用的部分。里面的代码会原样插入到_jspService方法中。它适合执行一些简单的、与视图渲染紧密相关的逻辑但绝不适合处理复杂业务。重要禁忌避免在脚本片段中编写数据库访问、复杂的业务计算。这会导致JSP页面职责过重难以测试和维护。我见过一个JSP文件里有长达数百行的SQL查询和业务逻辑那简直是维护的噩梦。表达式Expression% Java表达式 %用于输出一个Java表达式的值到HTML中。表达式会被计算结果转换为字符串然后输出。它相当于out.print(表达式)。注意表达式末尾没有分号。声明Declaration%! 变量或方法声明 %这里声明的变量或方法会成为生成的Servlet类的成员变量或成员方法。这意味着它们不在_jspService方法内。实操心得声明成员变量需要非常小心因为它不是线程安全的多个请求线程会共享这个成员变量可能导致数据错乱。除非你非常清楚自己在做什么比如定义一个常量或同步方法否则应尽量避免使用声明。4.2 指令Directive控制JSP页面行为指令为容器提供整个页面的设置信息不会产生任何输出。page指令% page attributevalue %这是最重要的指令。常用属性包括contentType设置响应MIME类型和字符编码如% page contentTypetext/html;charsetUTF-8 %。解决中文乱码问题首先检查这里和pageEncoding。pageEncoding指定JSP文件自身保存时使用的编码与contentType的charset保持一致是良好实践。import导入Java类多个类用逗号分隔。如% page importjava.util.List, com.example.User %。session默认为true表示页面参与HTTP会话。如果设为false则该页面无法使用session内置对象。errorPage/isErrorPage用于配置错误处理页面。include指令% include fileheader.jsp %这是静态包含。在翻译阶段被包含文件的内容会被原封不动地插入到当前JSP中然后一起被翻译成一个Servlet。适用于包含不会变化的公共片段如页头、页脚、静态菜单。注意因为是在翻译期合并所以被包含文件中定义的变量在当前文件中可以直接使用。修改被包含文件后需要等待容器重新翻译包含它的所有JSP页面。taglib指令% taglib uri... prefixc %用于引入标签库如JSTLJSP Standard Tag Library。这是现代JSP开发中减少脚本片段的关键通过类似XML的标签来完成循环、条件判断、格式化等操作。4.3 动作Action动态行为标签动作是在请求处理阶段执行的以XML标签形式书写。jsp:include动态包含。它会在当前页面执行时将另一个资源JSP或Servlet的输出结果包含进来。被包含的资源独立编译和执行。与静态包含的区别% include %是源代码级别的合并jsp:include是运行时输出的合并。后者更灵活开销稍大且被包含页面的变量不共享。jsp:useBeanjsp:setPropertyjsp:getProperty用于操作JavaBean。在早期Model 1架构中常用现在基本被EL表达式和MVC框架取代。jsp:forward将当前请求转发给另一个资源JSP或Servlet处理。类似于Servlet中的RequestDispatcher.forward()。转发后由目标资源生成响应地址栏URL不变。4.4 EL表达式与JSTL告别脚本的现代方式这是让JSP保持生命力的关键扩展。ELExpression Language${expression}提供了一种简洁、强大的方式来访问作用域page, request, session, application中的属性、JavaBean属性、集合元素等。它自动处理null值比% %更安全、更优雅。 例如${user.name}等价于% user.getName() ! null ? user.getName() : %。JSTLJSP Standard Tag Library 通过taglib指令引入后可以使用一系列标准标签。核心标签库Core前缀通常为c。包含c:if,c:forEach,c:choose,c:when,c:otherwise,c:set,c:out等。c:out默认会对输出进行XML转义防止XSS攻击比直接使用${}或% %更安全。格式化标签库Fmt用于国际化消息、格式化日期和数字。函数标签库Functions提供一些字符串处理函数如fn:contains,fn:substring等。最佳实践建议在新的或需要维护的JSP页面中彻底放弃脚本片段Scriptlet。所有数据展示用EL表达式所有逻辑控制用JSTL标签。如果页面逻辑依然复杂那说明这部分逻辑应该移到后端的Servlet或Controller中JSP只负责接收模型数据并渲染视图。这才是健康的MVC模式。5. 开发环境搭建与常见问题实战排查即便只是维护老项目一个顺手的开发环境也能极大提升效率。这里以最通用的IntelliJ IDEA Tomcat组合为例讲解关键配置和避坑点。5.1 环境搭建要点项目创建与模块设置在IDEA中创建Java Enterprise项目选择Web Application模板。确保模块的Facets中包含了Web并且Web Resource Directories指向了正确的目录通常是src/main/webapp或web。这是IDEA识别JSP和应用结构的基础。Tomcat服务器配置Run - Edit Configurations添加Tomcat Server - Local。在Deployment选项卡通过 - Artifact添加你的Web应用构件。这里的关键是Application context它决定了你的应用根路径如/myapp。在Server选项卡可以配置端口、启动超时时间等。如果遇到“Application Server was not connected before run configuration stop”这类错误通常是因为Tomcat实例没有成功启动检查JDK版本兼容性和端口占用。依赖管理如果使用Maven在pom.xml中JSP和Servlet相关的API依赖作用域应设为provided因为它们由Tomcat等容器提供。dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version !-- 版本需与Tomcat匹配 -- scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL依赖作用域通常是compile -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency5.2 高频问题排查实录根据提供的热词我整理了几个最常见的问题场景和解决思路问题现象可能原因排查步骤与解决方案Tomcat启动成功但访问JSP报4041. JSP文件未放在Web应用的正确目录下应在webapp或其子目录。2.web.xml中配置了错误的欢迎页或Servlet映射覆盖了JSP。3. 应用上下文路径Context Path配置错误。1. 检查JSP文件物理位置。2. 检查web.xml确保没有*.jsp的Servlet映射拦截了请求。3. 在IDEA的Run Configuration或Tomcat的server.xml/context.xml中检查应用上下文路径。JSP页面中文乱码1. JSP文件自身保存编码与pageEncoding指令不符。2.page指令的contentType未设置或字符集错误。3. Tomcat服务器连接器Connector未配置URI编码。4. 数据库连接字符集不匹配。1. 确保JSP文件以UTF-8保存并添加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8%。2. 在Tomcat的server.xml的Connector标签中增加URIEncodingUTF-8。3. 检查获取请求参数时是否使用了request.setCharacterEncoding(UTF-8)对POST有效。“java.lang.OutOfMemoryError: Java heap space”1. 应用内存泄漏如不当使用%! %声明大对象、静态集合持续增长。2. JSP页面过大或编译生成的类过大。3. Tomcat分配的堆内存不足。1. 使用Profiler工具如VisualVM分析内存堆转储查找泄漏点。2. 拆分过大的JSP页面。3. 调整Tomcat启动参数CATALINA_OPTS或JAVA_OPTS增加-Xmx最大堆内存值。修改JSP后浏览器刷新看不到变化1. 浏览器缓存。2. Tomcat未启用开发模式热部署。3. 旧的.class文件被缓存。1. 强制刷新浏览器CtrlF5。2. 检查Tomcat的context.xml确保Context reloadabletrue生产环境慎用。3. 手动删除Tomcat的work目录下对应应用的缓存文件重启Tomcat。“The absolute uri: [http://java.sun.com/jsp/jstl/core] cannot be resolved”1. 未导入JSTL的jar包。2. jar包版本与taglib指令中的URI不匹配。3. jar包未放在正确位置WEB-INF/lib。1. 确认jstl-xxx.jar和standard-xxx.jar对于旧版本已添加到项目依赖并部署到WEB-INF/lib。2. 对于JSTL 1.2使用URIhttp://java.sun.com/jsp/jstl/core。检查版本对应关系。在JSP中使用Ajax传递参数出错1. 参数未正确序列化或编码。2. 后端Servlet/JSP未正确设置响应类型。3. 路径错误。1. 使用JSON.stringify()发送复杂数据并设置contentType: application/json。2. 后端确保response.setContentType(application/json;charsetUTF-8)并使用如Gson库输出JSON字符串。3. 检查Ajax请求的URL是否为正确的后端处理地址。5.3 性能调优与安全考量即使只是维护了解一些调优和安全点也很有必要。性能方面预编译JSP在生产环境中可以在应用启动或部署时通过工具如Ant任务、Maven插件或Tomcat配置jspPrecompilation预编译所有JSP避免第一个用户触发编译带来的延迟。精简JSP页面避免在JSP中进行大量计算和IO操作。移除无用的导入和标签库。合理使用包含静态包含% include %在翻译期合并适合不变的片段动态包含jsp:include更灵活但稍有开销根据场景选择。安全方面XSS防护永远不要信任用户输入。使用JSTL的c:out标签输出用户数据它会进行HTML转义。如果确实需要输出原始HTML如富文本编辑器内容必须进行严格的白名单过滤。禁用脚本如果确定不再需要可以在web.xml中全局禁用JSP脚本强制使用EL和JSTL。jsp-config jsp-property-group url-pattern*.jsp/url-pattern scripting-invalidtrue/scripting-invalid /jsp-property-group /jsp-config错误信息暴露确保生产环境的web.xml中配置了自定义错误页面error-page避免将包含堆栈跟踪的详细错误信息直接暴露给用户。6. JSP在现代开发中的定位与迁移思考时至今日全新的项目几乎不会选择JSP作为主要视图技术。前后端分离架构前端使用Vue/React/Angular后端提供RESTful API已成为绝对主流。那么JSP还有价值吗有的主要在以下几个场景遗留系统维护大量银行、电信、政府、传统企业的内部系统基于Struts、Spring MVC等框架使用JSP作为视图层。理解和维护它们是许多Java工程师的日常工作。快速原型或简单内部工具对于一个小型、无需复杂交互的内部管理页面用JSP快速撸一个可能比搭建完整的前后端分离项目更快捷。教学与理解原理学习JSP是理解Java Web开发演进史、Servlet容器工作原理、MVC模式起源的绝佳途径。如果你面对一个JSP老项目是该重构还是该迁移我的建议是分步骤、看情况第一步规范化与安全加固。在现有架构下先做最低成本的改进引入JSTL和EL全面替换脚本片段配置安全响应头添加CSRF防护对输出进行转义。第二步模块化剥离。如果系统庞大可以尝试将新的功能模块采用前后端分离的方式开发通过子域名或路径与老系统集成。逐步蚕食而非一次性重写。第三步视图技术平移。如果决定重构但保留后端MVC框架如Spring MVC可以考虑将JSP页面逐步重写为Thymeleaf或FreeMarker模板。这些模板引擎更现代、功能更强且与Spring生态集成更好但模板渲染的基本思路是相通的。彻底重写只有当业务逻辑也极度混乱且公司有足够资源时才考虑用全新的前后端分离架构重写。这是一项系统工程需要谨慎评估。最后关于学习路线如果你是一名初学者我建议的路径是Servlet - JSP理解原理和基本语法即可 - JSTLEL - Spring MVC - 前后端分离框架。跳过JSP直接学Spring Boot也可以但当你遇到那些“古老”的术语和概念时回头看看JSP你会对Web开发有更立体、更深刻的理解。技术的新旧更替是常态但理解底层原理和设计思想是工程师应对变化最坚实的底气。在维护那些布满岁月痕迹的JSP页面时我常想它们不仅是代码更是一个时代的开发思想与局限性的缩影读懂它们也就读懂了今天我们为何要如此设计软件。