ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JSP为何被淘汰:从模板引擎到前后端分离的Java Web演进

JSP为何被淘汰:从模板引擎到前后端分离的Java Web演进 1. 从一段维护经历说起JSP 当年凭什么活着前几天帮一个朋友排查老系统的问题项目是基于 Spring MVC JSP 的遗留系统跑在 WebLogic 上面。页面打开慢、后台报错信息不明不白问题出在哪都找不到。我一打开那个 JSP 文件满屏的% ... %标签穿插在 HTML 里循环里还夹着几十行 Java 代码看得人头皮发麻。这场景放在十五六年前实在是再正常不过了。那会儿 Java Web 开发基本就等于“写 JSP”。做网站就是这么个流程用 Dreamweaver 切个 HTML 页面把动态数据的位置留好再用% request.getAttribute(xxx) %往页面里塞数据扔进 Tomcat 就能跑起来。Servlet 负责接收请求、处理逻辑JSP 负责渲染页面两者通过 request、session 这些对象打通一套 MVC 结构就这么搭完了。很多人问JSP 到底是不是被淘汰了严格来说它没有被官方宣判死刑仍然能跑还有一部分老系统在用。但“很少人用”和“几乎不用于新项目”是两回事。现在 Java 服务端渲染的默认选择早就变成了 Thymeleaf、FreeMarker或者干脆后端完全不管页面直接返回 JSON 给前端。JSP 的退场不是某一天突然发生的而是前后端开发模式、工程化程度、分工方式同时改变之后它逐渐变得不合时宜了。想明白“为什么没人用了”得先搞清楚当年它解决了什么问题又是在哪些环节上跟不上时代。2. JSP 的黄金年代一个动态网页能跑起来的关键拼图2.1 JSP 的底层机制到底是怎么回事JSP 全称是 JavaServer Pages本质上是将 Java 代码嵌入 HTML 的模板技术。第一次请求一个.jsp文件时容器比如 Tomcat会把它翻译成一个 Servlet 的 Java 源文件再编译成 class然后执行。你看 JSP 的.jsp文件像是视图代码其实它在服务端跑起来时就是一个 Servletout.println()把 HTML 一行一行地写给浏览器。这里面有一个让很多初学者容易困惑的点JSP 里的内容并不都是“展示内容”。%! %声明的是成员变量和方法% %里写的是 Java 业务逻辑% %是用来输出表达式的值。再加上 JSTL 标签库和 EL 表达式JSP 就有了循环、判断、遍历集合的能力。一个经典的个人信息展示页可以写成这样% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title个人信息/title /head body h2用户信息/h2 p姓名% request.getAttribute(name) %/p p年龄% request.getAttribute(age) %/p c:if test${not empty user.emails} ul c:forEach items${user.emails} varemail li${email}/li /c:forEach /ul /c:if /body /html这种写法在今天看来很粗糙但放在当时的环境里它确实是最高效、最直接的选择。后端人员不需要会写复杂的前端代码Java 逻辑直接嵌到页面里刷新一下浏览器就能看到变化。2.2 当年的开发模式为什么离不开 JSP2005 年前后的 Web 开发和今天完全是两个世界。浏览器能力有限JavaScript 更多的是做表单校验、下拉菜单这种点缀效果不存在“前端工程”这个概念更没有 Node.js。服务端不仅要处理业务还要负责把页面组装好再整体返回给浏览器。那时候如果有简单直观的模板语言能把 Java 对象的数据渲染进 HTML开发者的效率就非常高了。JSP 实际充当了“模板引擎”的角色但它的能力比普通模板引擎更强可以写 Java 代码可以访问 request、session、application 这些隐式对象还可以借助jsp:useBean、jsp:include、jsp:forward这类标签做页面组合和跳转。再加上 JSP 背后的 Java EE 生态——JDBC 连数据库、Struts 做 MVC、后来又有 Spring MVC一套标准的站长式后台管理系统的开发流程很快就固定下来了。登录页、列表页、详情页、表单页都是 JSP 一把梭。我记得当时做个新闻发布系统从数据库取数据到页面循环输出半天时间就能搞定这种“所见即所得”的开发体验是那个时代最典型的特征。2.3 为什么当年觉得它“风光无限”JSP 风光的核心原因是它的便宜和直接。今天写一个页面要先搭 Node 环境、配 webpack、设计接口、做前后端联调而当年的 JSP 开发只需要一个 Tomcat、一个 IDE 就能跑通全流程。对于中小型项目而言业务逻辑、页面渲染、数据库访问全部可以在一个 WAR 包里面完成部署就是把包丢进 webapps 目录启动容器完事。而且 JSP 天然地复用了 Java 企业级生态。碰到复杂页面还可以自定义标签库把重复的代码片段收拢成标签。大型 Java 项目也有一套自洽的约定Servlet 做控制器JavaBean 做模型JSP 做视图谁做什么都清清楚楚。这套模式确实统治了 Java Web 开发超过十年也培养了整整一代后端工程师。你去看 2010 年以前出版的 Java Web 教材几乎所有书都是以 JSP 为核心的。3. 时代转向前后端分离是怎么一步步抽走 JSP 地基的3.1 Ajax 出现页面开始有自己的“姿态”让 JSP 开始感到不适的第一个冲击来自 Ajax。因为浏览器可以异步请求接口、局部更新页面View 层的职责就不再是每次都必须返回完整 HTML。服务端开始输出 XML、JSON 这种纯数据前端拿到数据之后自己去更新 DOM。道理很简单既然后端可以只给数据为什么还要一层 JSP 去拼页面已经拼好的完整 HTML 到了浏览器端还要被 JavaScript 局部替换这不是脱裤子放屁吗所以从那时起一些敏捷的团队开始把页面静态化或者用纯 HTML 交给前端后端只专心提供 API。JSP 在这种模式里显得非常尴尬它的开发效率优势一旦被“纯数据接口 前端渲染”替代剩下的就只有复杂度了。3.2 前端工程化的兴起彻底改变了协作模式到了 2015 年以后React、Vue、Angular 这批前端框架开始流行。前端工程师拥有了自己的构建工具链npm 管理依赖webpack 打包资源代码组件化、模块化。这时候前后端协作变成了两个相对独立的团队前端负责页面后端负责接口接口契约定好以后两边可以并行开发互不阻塞。JSP 的致命问题就在这里暴露了它把前端代码强行塞进了一个 Java 服务端工程里。改一行样式可能要去 Java 工程师的代码库里改 JSP发布时还要跟着整个 WAR 包一起构建。前端工程师想要用 Sass、Less、热更新、组件化这些现代化的开发体验JSP 完全给不了。更麻烦的是如果页面里嵌的 Java 逻辑出了问题前端和后端要一起开会排错边界极其模糊。3.3 前后端分离带来了什么实际收益我把这层变化总结为“三个解耦”。第一个是开发解耦前端工程师和后端工程师不需要在同一个代码库里互相踩脚了前端在本地跑 dev server模拟接口返回数据后端启动自己的服务两边只是约定一份接口文档。第二个是部署解耦静态资源可以走 CDN后端服务可以独立扩容JSP 那种所有资源挤在一个应用里的打包方式不再适用。第三个是技术栈解耦后端团队可以继续用 Java、Go前端团队换成 Vue、React谁都不干涉谁。在这种体系下JSP 的能力集合完全被覆盖掉了页面渲染被前端框架接管模板语法被数据的双向绑定和虚拟 DOM 取代服务端动态拼接 HTML 的活如果不是为了 SEO 或首屏速度基本没有存在的必要。JSP 从“核心组件”变成了“历史包袱”再也没有人会拿它写一个新的前后端分离项目。4. 从技术硬伤看看JSP 为什么留不住新项目4.1 JSP 的编译模型带来了性能和管理负担先说一个很多人忽略的问题JSP 在服务器上不是直接运行的。Tomcat 每收到一个对.jsp的请求要先判断这个 JSP 文件是否被修改过如果修改过就要重新翻译成 Servlet 源码、重新编译。虽然容器做了缓存但在大型页面很多、流量又高的系统里这种运行时编译机制依然会造成 CPU 峰刺特别是在刚重启后第一次访问某个页面的时候。对比后来的模板引擎比如 Thymeleaf、FreeMarker虽然也是服务端渲染但它们本质上是把模板文件解析成模板对象再通过数据模型输出不走“Java 源代码动态生成”这条链路。而 JSP 本质上是动态生成 Java 类从编译、加载、实例化到执行整个过程就在请求路径上出了问题也更容易暴露在运行环境。4.2 混合代码导致调试和测试变得极其痛苦你要是维护过一个老 JSP 项目一定懂这种痛苦。页面顶部是一段 Java 代码连接数据库HTML 中间嵌着% for(...) { %结尾又用% } %来闭合循环稍不留神括号就错位了。出问题时报错信息显示的是一长串编译后的 Servlet 堆栈根本对不上 JSP 原始文件的行号排查一次要花大量时间。这在“面向测试的工程化体系”里是不可接受的。前端逻辑应该能走单元测试页面组件应该能组件化地复用JSP 做不到或者说做得非常勉强。它的代码边界是碎裂的一个 JSP 页面可以同时包含业务逻辑、数据访问、页面渲染、前端脚本几乎没法做任何自动化测试。想要保证质量只能靠人工回归这在大型项目里就是灾难。4.3 模板技术本身没有“组件化”的内建能力JSP 虽然可以jsp:include包含公共页面也有 taglib 可以做自定义标签但这种“组件化”的粒度非常粗糙本质上还是服务端的文件拼接。现代前端强调组件化每个组件自带模板、样式、逻辑可以嵌套、可以传 props、可以管理状态。JSP 世界里做一套可复用的功能块要去定义 tag 文件、配置 tld交互灵活性也远不如 JavaScript 组件。这就形成了一个恶性循环项目越小用 JSP 越省事项目越大JSP 的维护成本就越失控。当技术选型者面前摆着一堆天生就是为“大型复杂页面”设计的前端框架谁还会回头选 JSP答案显而易见。4.4 微服务和容器化架构进一步挤压了 JSP 的生存空间微服务把庞大的单体应用拆成了很多个独立服务每个服务只负责自己的领域能力。这种架构下服务端倾向于提供纯粹的 REST API用 JSON、Protobuf 做数据交换页面层被剥离到前端应用中。JSP 这种“服务端渲染整个页面”的设计天然是单体应用的底座逻辑和微服务的边界划分完全匹配不上。容器化部署也让 JSP 的笨重更加明显。前端资源应该打成静态镜像用 Nginx 部署后端服务打成另一个镜像彼此独立扩容。如果还是一整个 WAR 包内含 JSP每次前端改动都要重新构建整个后端服务发布效率低到令人抓狂。我见过很多团队简单改个按钮颜色都要等 Java 构建几分钟这在前后端分离的体系里是完全不可想象的。5. 老技术的现实坐标今天还有哪些场景用得着 JSP5.1 遗留系统维护是不容忽视的常规战场我前面说了那么多 JSP 的坏话但如果你的工作恰恰是维护一个老系统那 JSP 依然是实际问题。银行、政务、能源、传统制造业这些行业的信息化系统起步早核心业务跑了很多年代码稳定想迁移也不是一朝一夕的事。它们的门户网站或者后台管理模块很可能还在用 JSP。在这种场景里你是绕不开 JSP 的。所以我的建议是不要只把它当“过时技术”嗤之以鼻而是要掌握基本的 JSP 维护能力——看得懂% %脚本片段会用 EL 表达式和 JSTL知道怎么排查 JSP 编译报错。哪怕只是为了“能干活”这些知识也有实实在在的价值。5.2 学 JSP 到底还有没有意义从学习路径来说我不建议新人专门深入钻研 JSP 的高级自定义标签或者各种隐式对象之间的细微差异但我强烈建议你理解 Java Web 的基础脉络。因为 JSP 是理解 Servlet 生命周期、理解 Web 容器如何处理请求的绝佳入口。你搞明白了 JSP 先被编译成 Servlet再加载执行自然就明白了 Java Web 应用的本质。很多招聘岗位还会要求“熟悉 JSP”或者“有老系统维护经验”如果你完全没接触过面试时是会吃亏的。哪怕你日常用的是 Spring Boot Vue也应该花一两天时间把 Servlet、JSP、Filter、Listener 这条线过一遍这是 Java Web 体系的根框架只是长在根上的花。了解这些历史不是为了回到过去而是为了在遇到老系统时不慌在涉及服务端渲染选型时也能做出更清晰的判断。6. JSP 热搜词里藏着的新手刚需四个现场问题一次说清6.1 高频率被搜的“jsp个人信息展示页面”这是拿 JSP 做后台管理系统时最常见的需求场景。很多刚接触 JSP 的开发者其实是想知道怎么把 Java 后台的数据循环渲染到表格或卡片里。核心方法是分清三件事用 EL 表达式${}取数据用 JSTL 标签c:forEach遍历和c:if做判断不要在页面里写一大坨% %Java 代码。哪怕你是在修老代码也应该尽量把新写的部分改成 EL JSTL这样页面清爽、可读性高。一个典型的列表展示页可以这样组织% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table thead trthID/thth姓名/thth邮箱/th/tr /thead tbody c:forEach items${userList} varuser tr td${user.id}/td td${user.name}/td td${user.email}/td /tr /c:forEach /tbody /table关键不在于代码本身多复杂而在于你必须保证 JSP 里拿到的userList是 Controller 放入 request 域中的对象。如果后端代码写的是request.setAttribute(userList, list)JSP 里就可以直接用${userList}访问。配合常用的c:if还能做空数据提示这样页面就不会露出难看的空白区域。6.2 高频问题“jsp图片如何对坐标定位”这个问题问的人特别多但本质上它压根不是 JSP 的问题而是 CSS 的问题。JSP 输出的图片本质就是img标签坐标定位无非是 CSS 的position属性。你需要在哪个页面容器里定位就在那个容器上用相对定位然后用绝对定位把图片摆到指定坐标。比如要在个人信息卡片右上角显示一枚头像徽章div styleposition: relative; width: 200px; height: 200px; img src${user.avatar} styleposition: absolute; left: 150px; top: 20px; width: 50px; height: 50px; / /div这里position: relative写在父容器上是为了让absolute定位的子图片以父容器为参照系。如果你忘了给父容器写relative图片就会跑到浏览器窗口的相应坐标去这也是这类定位问题中最常见的坑。还有一点要留意图片坐标定位时left、top的数值单位可以是像素也可以是百分比在小屏幕适配时百分比往往比定死像素更稳。6.3 “jsp页面让加载完后刷新一次”如何实现这个需求听着奇怪实际场景却很常见。比如后台提交表单之后想回到列表页时让页面自动刷一次拉取最新数据或者某个页面在定时刷新需要在页面加载完成后触发一次强制刷新。实现方式有两种效果略有不同。第一种是用meta标签做定时刷新meta http-equivrefresh content1content 里的数字是秒数1 表示 1 秒后刷新一次页面。这种方式优点是够简单无需 JavaScript适合“每隔固定时间就刷新”的需求。缺点是只要这个 JSP 被加载它就会无限循环刷新下去如果只是想“加载完成后只刷新一次”它反而不合适。第二种是用 JavaScript 在页面加载完毕后延时刷新script window.onload function () { setTimeout(function () { location.reload(); }, 1000); }; /script这段代码的意思是页面上的所有资源加载完成之后等 1 秒钟再调用location.reload()刷新。和meta方式最大的区别是它只刷一次因为刷新后window.onload又执行一次然后又会设置一个 1 秒后的定时刷新。如果你确实只想刷新一次需要加一个标识控制防止无限循环。最简单的做法是用 cookie 或 sessionStorage 记一个标志script if (!sessionStorage.getItem(hasRefreshed)) { sessionStorage.setItem(hasRefreshed, 1); setTimeout(function () { location.reload(); }, 1000); } /script用 sessionStorage 的好处是只在当前浏览器会话里有效关闭页面重开之后又会重新刷新一次。很多新手不知道这一点直接写成location.reload()放在onload里结果页面疯狂刷新最后只能强制关掉标签页。改明白这个标志位的逻辑之后这个需求其实非常容易控制。6.4 “饿了么elment图标前端jsp”到底是什么需求这个热词里把 “element” 拼成了 “elment”原意大概率是饿了么团队开源的 Element UI 组件库中的图标想用在 JSP 页面里。Element UI 是 Vue 组件库图标组件el-icon原本依赖 Vue 环境。如果你在一个纯 JSP 项目里用不可能直接引入整个 Element UI 的构建链路但图标本身是 SVG/字体文件完全可以脱离 Vue 单独使用。最简单的方案有两种。第一种是直接引入 Element UI 的 CDN 文件然后在 HTML 里使用i classel-icon-edit/i这类写法样式类名会通过字体或 CSS 生效。第二种是把图标对应的 SVG 源码直接复制进 JSP 页面比如需要“编辑”图标就去官方文档找到 SVG 代码内联到i或者svg标签里完全不依赖外部框架。我更推荐第二种因为 JSP 项目一般不需要为几个图标引入整套组件库的样式纯内联 SVG 轻量、可控、不污染全局样式。这里顺便提醒一句Element UI 和 Element Plus 是两个不同时代的版本前者对应 Vue 2后者对应 Vue 3。在老 JSP 系统里做维护时要先确认你找的图标是不是当前技术栈能用的版本否则类名对不上图标自然显示不出来。如果实在遇到 CDN 资源和当前页面结构冲突把 SVG 代码内联进来永远是最不容易出错的兜底方案。7. 从 JSP 退场反推技术选型的原则技术选型的逻辑其实非常简单新项目优先考虑团队协作、工程质量、部署运维的目标而不是个人情怀。JSP 不是因为某个人宣布它死了才没落的是它在每一个维度上都输给了更合适的技术方案。性能、可测试性、组件化、脚手架化、前后端协作、容器化部署没有一个维度是它的强项。但我始终觉得“淘汰”这个词对 JSP 来说过于残忍。它更像是一位完成了历史使命的老前辈把 Java Web 开发从“手动打印 HTML”带到了“模板引擎 MVC”的阶段然后在更年轻的技术面前自然退位。今天你去看一些老系统还能看到它的身影你去搜一些维护教程还会看到大量 JSP 相关问题。这说明它依然在角落里为一部分系统稳定运行默默兜底。我自己在实际操作中的体会是掌握 JSP 不是坏事但别在不合适的地方硬用它。如果你正在维护老项目那就把它当作一门成熟的旧手艺平稳修好故障、控制好改动范围就是胜利。如果你在做新项目选型除非团队里所有人都极其熟悉 JSP 且没有前后端分离的需求否则真的别在新代码里再写 JSP 了。技术的生命力不在于它曾经多风光而在于它是否还能解决当下场景里的真实问题。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进