尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

深入解析JSP运行原理:从Servlet到动态网页的演进与实践

深入解析JSP运行原理:从Servlet到动态网页的演进与实践 1. 从Servlet到JSP一个历史视角的引入如果你在十多年前开始接触Java Web开发那么“JSP”这个词对你来说一定不陌生它几乎就是那个时代动态网页的代名词。即便在今天当你打开一些老旧的内部管理系统、学校教务网站或者某些传统企业的门户时背后很可能依然运行着JSP页面。最近我在排查一个老项目的兼容性问题时又和它打了一次交道这让我觉得有必要把JSP的核心脉络重新梳理一遍尤其是它的运行原理这对于理解整个Java Web技术栈的演进甚至解决一些当下的“历史遗留问题”都至关重要。简单来说JSPJavaServer Pages是一种用于创建动态Web内容的技术。它允许你将Java代码嵌入到HTML页面中。当用户请求这个页面时服务器会执行其中的Java代码生成最终的纯HTML再发送给浏览器。所以你在浏览器里看到的永远是静态的HTML、CSS和JavaScript但服务器端送出的每一份HTML都可能是“新鲜出炉”、因人而异的。这解决了早期纯Servlet输出HTML时那种需要在Java代码里疯狂拼接字符串out.println(html)的噩梦让页面逻辑和展示逻辑在一定程度上得以分离。2. JSP的本质与运行原理深度拆解很多人学了很久JSP可能依然觉得它很神秘。其实它的核心原理一句话就能说清JSP就是一个“披着HTML外衣”的Servlet。服务器所做的一切都是为了把这个“外衣”扒掉还原出它Servlet的本来面目并执行。2.1 JSP的生命周期翻译与编译一个JSP文件例如index.jsp从你编写完成到在浏览器中呈现主要经历两个关键阶段这两个阶段通常只在第一次访问或JSP文件被修改后发生。第一阶段翻译Translation当你第一次请求index.jsp时应用服务器如Tomcat中的JSP容器JSP Container会首先找到这个文件。它不会直接执行这个.jsp文件而是将其“翻译”成一个纯Java源代码文件。这个生成的Java文件是一个标准的Servlet类。翻译过程做了以下几件核心事情静态内容转换所有HTML、CSS、JavaScript等静态文本都会被转换成一系列out.write(“...”);语句。JSP元素处理% ... %脚本片段Scriptlet中的Java代码会被原封不动地移到生成的Servlet的_jspService方法中。% ... %表达式Expression会被转换成out.print(...);。% page ... %、% include ... %等指令Directive用于指导翻译过程比如设置页面属性、导入类、包含其他文件。jsp:useBean、jsp:include等动作Action会被转换成相应的Java代码。隐式对象注入在_jspService方法中会自动声明并初始化如request、response、out、session、application等9大隐式对象让你在JSP页面中可以直接使用。这个生成的Java文件通常会被放在Tomcat的工作目录下例如work/Catalina/localhost/你的应用名/org/apache/jsp/里文件名类似index_jsp.java。第二阶段编译CompilationJSP容器接着会调用Java编译器javac将这个index_jsp.java源文件编译成标准的Java字节码文件index_jsp.class。至此一个完整的Servlet就诞生了。注意很多初学者遇到的“ClassNotFoundException”或“500错误”就发生在这个阶段。如果你的JSP页面里写了错误的Java语法或者引用了不存在的类编译就会失败。此时查看Tomcat的logs/catalina.out或localhost.log通常能找到具体的编译错误信息这是排查JSP语法错误最直接的方式。2.2 请求处理Servlet的舞台一旦编译成功后续的请求就跳过了翻译和编译阶段。JSP容器会加载这个index_jsp.class创建其实例单例模式通常只实例化一次并像调用普通Servlet一样调用它。初始化容器加载Servlet类调用其init()方法仅一次。请求处理对于每个到达的HTTP请求容器会调用Servlet的service()方法。对于JSP生成的Servletservice()方法会路由到_jspService(HttpServletRequest, HttpServletResponse)方法。执行与输出在_jspService方法中之前嵌入的Java逻辑被执行数据库被查询业务逻辑被处理所有的out.write()和out.print()语句将最终的HTML内容写入响应流。销毁当应用卸载时调用destroy()方法。所以JSP的运行时性能与Servlet几乎无异因为最终跑的就是编译好的Servlet字节码。第一次访问的延迟主要来自于翻译和编译的开销。2.3 核心组件详解指令、脚本、动作与表达式要写好JSP必须理解它的几种核心语法元素它们决定了页面如何被翻译。1. 页面指令Page Directive以% page ... %形式存在用于定义整个JSP页面的属性。它必须放在文件顶部。常用属性包括contentType和pageEncoding设置响应MIME类型和页面文件编码。乱码问题80%源于这里设置错误或不一致。我习惯统一设置为% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%。import导入Java类多个类用逗号分隔。这是JSP中唯一需要导包的地方。session默认为true表示页面参与HTTP会话。如果设为false则页面中不能使用session隐式对象。errorPage和isErrorPage用于配置错误处理页面。2. 脚本元素Scripting Elements这是早期JSP中混合业务逻辑和展示逻辑的“万恶之源”但也提供了最大的灵活性。脚本片段Scriptlet% Java代码 %。其中的代码会直接插入_jspService方法。过度使用会导致页面难以维护俗称“意大利面条式代码”。表达式Expression% Java表达式 %。用于输出单个表达式的值会被转换为out.print(表达式)。表达式末尾不能有分号。声明Declaration%! 变量或方法声明 %。其中的代码会被插入生成的Servlet类中作为类的成员变量或方法而非在_jspService方法内。很少使用容易引起线程安全问题。3. 动作Action以XML标签形式存在在请求处理阶段被执行提供标准化的操作。jsp:include动态包含。在请求时包含另一个资源JSP或Servlet的输出。与静态指令% include %在翻译时合并源代码有本质区别。jsp:forward将请求转发给另一个资源处理。jsp:useBean、jsp:setProperty、jsp:getProperty用于操作JavaBean是早期MVC模式中连接控制器和视图的桥梁。4. 表达式语言EL与 JSTL这是为了取代笨拙的脚本片段而生的现代化技术它们本身不是JSP的原始部分但已成为JSP标准实践的核心。ELExpression Language${}语法。用于更方便地访问作用域page, request, session, application中的属性、JavaBean属性、集合元素等。例如${user.name}远比% ((User)session.getAttribute(user)).getName() %简洁安全。JSTLJSP Standard Tag Library一套标准标签库用于替代常见的Java脚本逻辑如循环c:forEach、条件判断c:if、格式化fmt:formatDate等。使用JSTLEL可以做到JSP页面中几乎不出现% ... %极大提升了可读性和可维护性。3. 实战从编写到部署的完整流程与避坑指南理解了原理我们通过一个简单的用户信息展示例子把流程走一遍。假设我们有一个User对象包含id和name属性从Servlet传递到JSP显示。3.1 环境准备与项目结构首先你需要一个标准的Java Web项目结构。我以最基础的IDE手动创建为例这有助于理解本质。MyWebApp/ ├── WEB-INF/ │ ├── web.xml (部署描述符Servlet 3.0后可省略) │ ├── classes/ (编译后的Servlet等Class文件) │ └── lib/ (依赖的Jar包如JSTL的jstl.jar和standard.jar) ├── index.jsp (欢迎页面) └── userInfo.jsp (我们的示例页面)关键点所有能被客户端直接访问的JSP、HTML、图片等资源都应放在WEB-INF目录之外。WEB-INF是一个受保护的目录客户端无法直接请求其中的文件这保证了配置文件和类库的安全。3.2 编写Servlet控制器创建一个UserServlet.java它模拟从数据库获取数据并转发到JSP。// 假设位于 src/com/example/web/UserServlet.java package com.example.web; import com.example.model.User; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; WebServlet(/userInfo) // Servlet 3.0 注解配置映射替代web.xml public class UserServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 模拟业务逻辑获取用户数据这里硬编码代替 User user new User(1, 张三); // 2. 将数据放入请求作用域供JSP读取 request.setAttribute(currentUser, user); // 3. 转发请求到JSP页面 RequestDispatcher dispatcher request.getRequestDispatcher(/userInfo.jsp); dispatcher.forward(request, response); // 注意转发是服务器内部行为浏览器地址栏不会变 } }3.3 编写JSP视图userInfo.jsp这是展示层我们使用EL和JSTL来保持页面干净。% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title用户信息/title /head body h1用户详情/h1 %-- 使用EL表达式安全地访问属性 --% p用户ID: ${currentUser.id}/p p用户姓名: ${currentUser.name}/p %-- 使用JSTL进行逻辑判断 --% c:if test${not empty currentUser} p用户对象存在。/p /c:if %-- 一个简单的循环示例 --% h3兴趣标签/h3 c:forEach vartag items${currentUser.tags} varStatusstatus p${status.index 1}. ${tag}/p /c:forEach %-- 假设User对象有tags列表属性 --% /body /html实操心得务必在页面顶部正确声明JSTL标签库% taglib ... %并确保lib目录下有对应的jar包。否则会报The absolute uri: [http://java.sun.com/jsp/jstl/core] cannot be resolved错误。现在更推荐使用Apache Tomcat Taglibs项目提供的实现或者Maven/Gradle管理依赖。3.4 部署与运行将整个MyWebApp目录复制到Tomcat的webapps目录下。启动Tomcat访问http://localhost:8080/MyWebApp/userInfo。过程回顾浏览器请求/userInfoTomcat根据WebServlet注解找到UserServlet。UserServlet执行doGet创建User对象并放入request属性转发到userInfo.jsp。如果是第一次请求userInfo.jspTomcat将其翻译编译为userInfo_jsp.java和.class然后实例化这个Servlet。调用userInfo_jsp的_jspService方法执行页面逻辑。EL表达式${currentUser.id}会被解析为((User)request.getAttribute(currentUser)).getId()。生成的HTML通过响应流返回给浏览器。4. 常见问题排查与性能优化经验谈在实际开发和维护中你会遇到各种各样的问题。下面是我总结的一些典型场景和解决方案。4.1 编译与运行时错误问题页面报500错误日志显示org.apache.jasper.JasperException: Unable to compile class for JSP。排查首先查看Tomcat的logs/localhost.yyyy-MM-dd.log文件找到具体的编译错误。常见原因语法错误JSP脚本片段中的Java代码有误。类找不到% page import... %的类不存在或项目WEB-INF/lib下缺少必要的JAR包。JSTL/EL表达式错误EL表达式引用了不存在的属性或调用了不存在的方法。问题页面显示乱码。解决确保“三层编码”统一为UTF-8JSP文件自身编辑器保存为UTF-8。通过% page pageEncodingUTF-8%声明。服务器输出通过% page contentTypetext/html; charsetUTF-8%设置响应头。浏览器解析上一步的响应头会指示浏览器用UTF-8渲染。也可以在HTML中加meta charsetUTF-8作为双保险。4.2 性能与并发考量首次访问慢这是正常的翻译编译开销。在生产环境可以通过应用启动后自动访问一次所有关键JSP页面称为“预热”来解决。脚本片段Scriptlet的线程安全在_jspService方法中定义的局部变量即在% ... %中定义的变量是线程安全的因为每个请求都有自己的栈帧。但是用%! ... %声明的成员变量是Servlet类的实例变量被所有请求线程共享是非线程安全的除非你非常清楚自己在做什么否则避免使用声明。隐式对象的作用域pageContext当前页面作用域最小。request一次请求链可能包含多次转发。session一次用户会话。applicationServletContext整个Web应用。应根据数据的生命周期将其存放在合适的作用域。滥用session会导致服务器内存压力增大。4.3 现代化替代与迁移思考虽然JSP仍在许多遗留系统中使用但新的Java Web项目大多已转向更清晰的前后端分离架构如Spring Boot Vue/React或现代化的服务器端模板引擎如Thymeleaf、FreeMarker。它们的主要优势在于更强的分离性Thymeleaf模板是纯HTML无需依赖Servlet容器即可在浏览器中静态预览。更优雅的语法功能强大且表达式更安全。更好的工具链集成与现代构建工具和框架无缝结合。如果你正在维护一个JSP老项目我的建议是不要轻易重写如果系统稳定业务逻辑复杂重写成本极高。渐进式改造第一步引入JSTL和EL彻底消除页面中的Scriptlet。这能立刻提升代码可读性和可维护性。第二步将复杂的Java业务逻辑从JSP中抽离移回Servlet或后端的Service层让JSP只负责简单的数据展示。第三步考虑将部分前端模块用现代技术如Vue组件替换通过AJAX与后端交互实现局部现代化。5. 开发工具与环境配置要点工欲善其事必先利其器。一个顺手的开发环境能避免很多低级错误。5.1 IDE的选择与配置IntelliJ IDEA Ultimate对Java Web支持最好创建War项目、配置Tomcat、调试JSP/Servlet都非常直观。社区版对Web支持有限。Eclipse for Enterprise Java免费且功能强大需要安装WTPWeb Tools Platform插件。VS Code轻量但需要安装“Extension Pack for Java”和“Tomcat for Java”等插件来支持JSP开发和调试。对于简单项目或学习来说足够。关键配置无论用哪个IDE核心都是正确关联JDK、Tomcat和你的项目。确保项目使用的JDK版本与Tomcat运行所需的JRE版本兼容。Tomcat 8.5通常需要JDK 7或以上Tomcat 10.x需要JDK 11或以上。5.2 Tomcat的配置与管理虚拟主机Virtual Host在conf/server.xml的Engine标签内配置Host可以将多个不同域名的应用部署在同一Tomcat实例。这对于本地模拟多环境很有用。Context配置除了把应用丢到webapps下还可以在conf/Catalina/localhost/下创建一个XML文件如myapp.xml通过Context标签指定应用的路径docBase和资源路径实现更灵活的部署。端口与连接器在conf/server.xml中修改Connector port8080 ...来改变默认端口。生产环境可能需要配置SSL连接器。5.3 依赖管理与构建工具对于稍正式的项目强烈建议使用Maven或Gradle来管理依赖如Servlet API、JSTL、数据库驱动等和构建过程编译、打包War。它们能帮你自动解决类库冲突和版本问题。一个典型的Maven Web项目结构如下my-webapp ├── pom.xml (Maven配置文件声明依赖) └── src └── main ├── java (Java源代码如Servlet) ├── resources (资源文件) └── webapp (Web应用根目录相当于之前的MyWebApp/) ├── WEB-INF │ └── web.xml └── index.jsp在pom.xml中你需要添加对javax.servlet-api提供Servlet类和jstl提供JSTL标签库实现的依赖并将它们的scope设置为provided因为Tomcat本身已包含这些API的实现。最后关于JSP的学习我的体会是它像是一本Java Web开发的“活历史”。深入理解它的运行原理不仅能让你轻松应对那些尚未退役的老系统更能让你深刻理解Servlet容器的工作机制、请求-响应模型、以及MVC模式的原始形态。这种底层理解在你学习Spring MVC等现代框架时会提供坚实的基石让你明白那些注解和配置背后到底在发生什么。当你再遇到页面乱码、请求转发失败、作用域数据丢失这些问题时你看到的将不再是黑盒的错误提示而是一幅清晰的、从浏览器到服务器再到数据库的完整执行链路图。
返回列表