1. 项目概述:为什么现在还要学JSP,以及你将从这篇文章里得到什么
先别急着划走。我知道很多人一看到JSP这仨字母,第一反应是"这不是上个时代的东西了吗?现在不都是前后端分离、Vue/React一把梭吗?"——这话没毛病,但问题是,不管前端框架怎么变,很多高校的JavaWeb课程、毕业设计、甚至一部分企业内部老系统的维护,依然绕不开JSP。
我前阵子刚帮一个朋友从他的毕业设计里把一堆JSP页面改造成前后端分离接口,改到一半的时候我就想,如果当初他能把JSP项目的基本原理吃透,改造起来根本不用这么费劲。
更要命的是,很多新手在IDEA里创建JSP web项目,按着网上搜到的教程一步步点,结果发现版本不一样、菜单对不上、依赖下载失败、Tomcat配了半天跑不起来……最后全都卡在环境搭建上,连JSP长什么样都没见到。
这篇文章我打算用一套完整可复现的流程,带你从零开始,在IntelliJ IDEA里创建一个标准结构的JSP web项目,涵盖Maven工程搭建、Tomcat配置、JSP页面编写、Servlet交互、部署运行和常见报错排查。我还会解释每一步背后的原因——比如为什么用Maven而不是直接创建Java项目、为什么JSP本质上是Servlet、为什么你的页面中文乱码的根源不在JSP文件编码而在这几个地方。这些原理层面的东西,才是你遇到问题能自己解决的核心能力。
适合阅读这篇文章的读者有三类:刚学完Java基础、准备进入JavaWeb开发的初学者;被学校课程要求用JSP做课程设计、毕业论文的在校生;以及工作中突然要接手一个老旧JSP项目、需要快速上手的开发者。
2. 项目创建前置准备:JDK、Tomcat和IDEA的设置,一个都不能少
2.1 为什么需要Tomcat,用生活类比理解Servlet容器
在动手创建项目之前,我们要先搞明白一个基础问题:为什么JSP项目离不开Tomcat?
你可以把Tomcat想象成一个"餐厅的营业厅"。Servlet和JSP就是你餐厅里干活的大厨,而你自己写的Java代码,是给大厨的菜谱。大厨(Servlet)不能直接在街上炒菜,他必须在营业厅(Tomcat)里,按照规定的位置(URL映射)、规定的流程(请求-响应模型)来工作。
换句话说,Tomcat是一个Servlet容器——它负责接收浏览器发送过来的HTTP请求,解析请求内容,然后找到对应的Servlet或者JSP页面去处理,处理完再把生成的HTML响应回传给浏览器。没有这个容器,你的JSP页面根本没人去执行它。
我记得自己第一次接触这个的时候也很懵,心想我写个HTML文件直接双击打开不就行了?为什么JSP就不行?原因是:HTML是静态文件,浏览器直接解析就能显示;而JSP文件里有Java代码片段,浏览器不认这种混合格式,必须由Tomcat先把JSP翻译成Java文件,再编译成Class,然后执行,把执行结果以纯HTML的形式输出给浏览器。这个"翻译-编译-执行"的过程,理解成"把菜谱翻译成菜肴"更贴切。
2.2 JDK版本选择和IDEA版本适配建议
以前我们配置JSP项目,最稳妥的组合是JDK 8 + Tomcat 8/9 + IDEA社区版。那时候网上教程最多,遇到问题能找到现成答案。
但说实话,都2025年了,如果系统还没装任何开发环境,我建议直接JDK 17起步——原因有两个:第一,JDK 17是长期支持版本,IDEA最新几个版本对它的支持非常成熟,不会再出现版本警告弹窗;第二,Tomcat 10及以上版本已经全面适配Jakarta EE规范,和较新的JDK兼容性更好。
不过这里有个容易踩的大坑,我必须提前说清楚:Tomcat 10之后的包名变了。老项目里你写的import javax.servlet.http.HttpServlet,在Tomcat 10及以上版本里必须改成import jakarta.servlet.http.HttpServlet。如果选了Tomcat 10+,你还按以前的javax.servlet写代码,编译的时候一报一个错。
所以,如果你是跟着学校教材做作业,教材多半是基于Tomcat 8/9的,建议你安装Tomcat 9,对应JDK 8或JDK 11都行;如果你打算自己练手、对接新版生态,直接Tomcat 10.1 + JDK 17的组合,代码里用jakarta.servlet开头导入。
2.3 IDEA中配置Tomcat的完整流程
安装和下载IDEA、JDK、Tomcat这部分我就不赘述了,遇到问题去官网下对应版本就行。重点是配置这个动作本身,很多新手在这里卡住。
打开IDEA,进入Settings(Windows下File -> Settings,Mac下IntelliJ IDEA -> Preferences),在左侧导航里找到Build, Execution, Deployment -> Application Servers,点击右上角+号,选择Tomcat Server。
接下来会让你选择Tomcat的安装目录——注意,选的是Tomcat的解压目录,而不是里面的bin文件夹。选好之后,IDEA会自动识别版本号。如果你这步识别不到,通常是Tomcat压缩包没有正确解压,或者解压后目录层级有问题——标准结构是Tomcat目录下直接就能看到bin、conf、lib、webapps这几个文件夹,不要再多套一层目录。
配置好Server之后,还有一个容易忽略的地方:Settings -> Build, Execution, Deployment -> Build Tools -> Maven,确认IDEA里使用的Maven是"本地安装版本"还是"内置版本"。我建议用内置捆绑版本就行,重点是要把User settings file和Local repository指向你自己的配置位置,尤其是阿里云镜像仓库配置好之后,后续下载依赖速度天差地别——这个后面细讲。
3. 第一个关键决策:用Maven创建JSP项目,而不是普通Java项目
3.1 两种创建方式对比,Maven赢在哪
在IDEA里创建JSP项目,入门教程里主要有三条路:
- 在
New Project里选Java Enterprise,勾选Web Application模板; - 先建一个普通Java项目,然后手动右键添加
Web支持(Add Framework Support); - 用Maven构建一个
war类型的Web项目。
三种方式都能跑起来JSP,但我强烈建议你直接用Maven方式,原因很简单:依赖管理。
JSP项目要跑起来,必须引入Servlet API、JSP API这些依赖。如果你用普通Java项目,这些jar包需要自己去找、去下载、手动拷到lib目录里——版本一旦下载错,或者拷漏一个,运行时就是各种ClassNotFoundException。
而Maven方式只需要在pom.xml里声明几个依赖坐标,Maven自动从仓库下载并关联到项目,而且会把你下载到的依赖版本统一管理。更重要的是,未来的项目里你迟早要接触Spring Boot、Spring MVC这些框架,它们全都是基于Maven/Gradle构建的,你现在学会Maven结构,等于提前打通了后续所有Java项目的基础。
如果你图省事选了Java Enterprise模板,IDEA确实会帮你初始化Web目录,但那种方式生成的项目配置不够透明,出了问题不好排查。我的经验是,直接理解Maven项目结构,对你以后看任何Java开源项目都有帮助。
3.2 Maven项目结构里每一个文件夹是干什么的
用Maven创建完之后,你的项目骨架大概是这样的:
jsp-demo/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ // Java源代码目录 │ │ ├── resources/ // 配置文件目录(如数据库配置、日志配置) │ │ └── webapp/ // Web应用根目录 │ │ ├── WEB-INF/ │ │ │ └── web.xml // Web应用部署描述符 │ │ └── index.jsp // 默认首页 │ └── test/ │ └── java/ // 测试代码目录这个结构里,src/main/java放Servlet类、业务类;src/main/webapp是Web根目录,你写的JSP文件都放在这里;WEB-INF是受保护目录,浏览器不能直接访问它里面的资源,所以web.xml这种配置文件必须放这里。
web.xml是Web应用部署描述符,作用类似于"目录索引"——它告诉Tomcat这个应用有哪些Servlet、每个Servlet的URL路径是什么、加载顺序是什么、启动时加载哪些配置。在早期的Servlet规范里,没有这个文件应用就不知道如何路由请求;新版Servlet规范虽然引入了注解方式(@WebServlet),但作为新手,我建议你把web.xml留着,遇到兼容性问题时它是最稳的兜底方案。
3.3 在pom.xml中配置依赖包和Maven镜像
打开pom.xml,在<dependencies>节点里加入如下内容(我以Tomcat 9 + javax规范为例):
<dependencies> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP API --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL 标签库,JSP页面常用 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>注意scope标签用了provided,意思是这个依赖在编码和编译的时候需要,但最终运行时由Tomcat容器提供,不要再打包进你的WAR文件里,不然会和Tomcat自带的类冲突。这算是比较常见的坑,没多少人告诉你,报错表现是java.lang.LinkageError或者NoSuchMethodError,排查半天发现是重复的Servlet API jar包。
依赖下载慢的问题,解决办法是配置Maven镜像。在Maven的settings.xml的<mirrors>节点中加入阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>配置完之后,原本需要几分钟的依赖下载,基本上几十秒就搞定。这一步看起来和JSP没什么直接关系,但省下来的时间都是实实在在的。
4. 从零开始完整创建JSP Web项目实操
4.1 IDEA新建Maven项目的每一个动作
现在开始正经实操。打开IDEA,点击New Project。
第一步,左侧选择Maven,确保Build system选的是Maven,JDK选择你安装的版本(我这里是JDK 17)。Name填项目名,比如jsp-demo,Location会自动生成,可以自己改到顺手的位置。
第二步,IDEA会自动生成一个Maven项目,等待右下角进度条跑完,Maven完成初始化和依赖下载。如果这一步报错,回到Settings -> Maven检查本地仓库路径和镜像配置。
第三步,这个默认项目还没有Web目录结构,需要手动补上。在src/main目录下新建一个文件夹,名字必须是webapp(IDEA的Maven Web项目约定俗成的目录名)。在webapp下新建WEB-INF文件夹,并在WEB-INF里创建web.xml文件。
web.xml的基础模板如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>jsp-demo</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>需要注意,如果你的Tomcat是10.x,web.xml头部应该用xmlns="https://jakarta.ee/xml/ns/jakartaee"、version="5.0"这种命名空间,千万别混用,否则应用启动时解析描述符会报错。
第四步,在webapp目录下创建一个index.jsp文件。IDEA这里如果识别到了Web目录结构,会直接有New -> JSP/JSPF的选项;如果没有,就新建普通文件,文件名后缀改成.jsp。
一个用于测试的最小index.jsp:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <title>我的第一个JSP页面</title> </head> <body> <h1>JSP项目创建成功!</h1> <p>当前服务器时间:<%= new java.util.Date() %></p> </body> </html>这个页面里<%@ page ... %>是JSP指令,contentType和pageEncoding都设置成UTF-8,是防止中文乱码的第一道防线。<%= new java.util.Date() %>这种写法叫表达式输出,它会调用Java代码并把结果直接嵌入到HTML中输出。
4.2 配置Tomcat运行环境,启动项目并访问
项目创建好了,接下来要让它在Tomcat里跑起来。这是整个流程里最容易让人崩溃的环节,我逐步拆解。
点击IDEA顶部工具栏的运行配置下拉框(一般显示项目名或Add Configuration),选择Edit Configurations。点击左上角+号,往下翻找到Tomcat Server -> Local。
在Server标签页里,Application server选择你之前配置好的Tomcat;Open browser勾选后,启动完自动打开浏览器访问。HTTP port默认8080,如果被占用改成8081或任意空闲端口即可。
切到Deployment标签页,点击+号,选择Artifact,然后选中你的项目war包。注意这里有一个IDEA的惯性位置:如果你是直接新建的Maven项目,IDEA有时候不会自动生成war的Artifact,你会发现在列表里找不到它。
解决办法是:在File -> Project Structure -> Artifacts里,点击+号,选择Web Application: Exploded(开发模式,资源和代码热更新更快),From modules,选你的项目模块,然后OK。回到Run配置的Deployment里,+ -> Artifact,这次就能看到了。
Application context默认是/或者项目名,比如/jsp-demo。这个字段决定了访问路径——如果填了/jsp-demo,那么浏览器访问地址是http://localhost:8080/jsp-demo/;如果是/,直接http://localhost:8080/。
配置完成,点击运行按钮(绿色三角)。第一次启动可能会慢一些,观察IDEA底部Run控制台日志,看到类似Artifact is deployed successfully或者Server startup in [xxx] milliseconds的日志,说明启动成功。
然后浏览器访问你的项目地址,看到JSP项目创建成功!和动态时间,恭喜,你的JSP项目正式跑通了。
4.3 如何验证JSP项目不是"假跑通"
我知道有些人会在这里产生一个错觉:浏览器显示了页面,就觉得一切OK了。但如果只是把静态HTML塞进JSP文件里,那跟直接建HTML文件有什么区别?JSP的核心能力在于动态生成内容,所以我们要做一个简单验证:通过Servlet接收请求参数,再转发到JSP页面渲染,这样一套完整的请求链路才算真正跑通。
在src/main/java下建一个包,比如com.demo.servlet,然后新建一个HelloServlet.java:
package com.demo.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = req.getParameter("name"); if (name == null || name.trim().isEmpty()) { name = "JSP学习者"; } req.setAttribute("name", name); req.getRequestDispatcher("/result.jsp").forward(req, resp); } }然后在webapp下新建一个result.jsp:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <title>处理结果</title> </head> <body> <h2>你好,${name}!</h2> <p>这条数据是Servlet动态写入的。</p> </body> </html>这里有一个细节:JSP页面里我用的是${name},这叫EL表达式。Servlet用req.setAttribute把name放进请求作用域,JSP再用EL表达式把它取出来,这条链路能跑通,就说明你对JSP的"动态"和Servlet的数据流转有了基本的操作认知。
重启Tomcat,浏览器访问http://localhost:8080/jsp-demo/hello?name=张三,如果页面显示"你好,张三!",说明Servlet和JSP的协作完全正常。注意URL是/hello,配的是注解方式,而web.xml里没有配置这个Servlet,说明新版Servlet规范的注解驱动是生效的。
5. JSP核心语法速览:脚本、指令和内置对象
5.1 三种脚本元素:声明、表达式、脚本片段
JSP页面之所以能嵌Java代码,靠的是三种脚本元素,这是每个JSP开发者都必须掌握的基础。
声明(Declaration):格式
<%! ... %>,用来声明成员变量和方法。声明的内容会被翻译成Servlet的成员属性,所以它在多个请求之间是共享的,有并发状态问题——我见过有人拿这里存用户登录信息,结果一个用户登录,所有用户都"被登录"了,血泪教训。表达式(Expression):格式
<%= ... %>,它会计算Java表达式的值,然后作为字符串输出到页面上。注意末尾不能加分号,这是新手最容易写错的地方。脚本片段(Scriptlet):格式
<% ... %>,里面可以写任意Java语句,可以调用内置对象,可以定义局部变量。在JSP里,页面的_jspService方法中,这些片段是顺序拼接的。
你还需要理解这三者底层其实都被翻译成了Servlet类的方法代码。一个JSP页面在Tomcat里对应的Java文件,本质上是继承HttpJspBase的一个类,它把整个页面内容在_jspService方法里通过out.write()逐行输出,这也就是为什么JSP能动态拼接HTML。
5.2 九大内置对象:实际开发中常用的就那几个
JSP内置对象是容器给你准备好的、可以在脚本片段中直接使用的变量,不需要手动声明。九个内置对象中,日常开发和面试常抓的是这五个:
request(类型HttpServletRequest):获取请求参数、请求头、请求方法等;response(类型HttpServletResponse):设置响应头、跳转响应等;session(类型HttpSession):跨请求保存用户状态,比如登录后记住用户信息;application(类型ServletContext):整个应用全局共享的上下文信息;out(类型JspWriter):向页面输出内容,本质就是页面输出流。
还记得我们前面写的<%= new java.util.Date() %>吗?这个能力是基于out实现的——所有表达式输出,最终都是通过out.write()把内容写进响应缓冲区。
还有四个内置对象分别是page、pageContext、config、exception,前三个理解成页面自身信息和全局配置的载体,exception只在isErrorPage="true"的JSP错误页面里使用,平时用到的概率不大。
5.3 JSTL标签库:JSP页面里不要写一堆Java代码
其实我前面讲脚本元素的时候心里是有点矛盾的——因为实际开发中,尤其是配合Servlet的MVC结构里,我们不推荐在JSP页面里写大量Java代码片段。原因有两点:一是Java代码和HTML混在一起,页面维护性极差;二是前端同事看到JSP文件里全是Java代码会直接崩溃。
推荐的做法是用JSTL(JSP标准标签库)来替代大部分脚本片段。我们在pom.xml里已经引入了JSTL依赖,用的时候在JSP页面顶部引入标签库:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>常用标签有:
<c:if test="${condition}">:条件判断;<c:forEach items="${list}" var="item">:循环遍历集合;<c:out value="${name}" />:输出内容,同时自带HTML转义,防止XSS攻击,比直接用${name}更安全。
一个简单的使用示例:
<table border="1"> <c:forEach items="${userList}" var="user" varStatus="status"> <tr> <td>${status.index + 1}</td> <td>${user.name}</td> <td>${user.age}</td> </tr> </c:forEach> </table>这样页面里的Java代码能减少90%以上,代码可读性也强得多。
6. 开发过程中必踩的四个大坑及完整排查方案
6.1 中文乱码问题,根子不在JSP文件编码
这是JSP开发里出现频率最高的报错,但它通常不止一个原因。乱码的完整链条是:JSP文件本身的编码 -> 页面响应的编码 -> 请求参数的编码 -> 数据库连接的编码,四环环环相扣,任何一环脱节都会乱码。
针对页面输出,最简单有效的方式是在JSP第一行设置:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>针对请求参数(尤其是POST请求),在Servlet里获取参数前加上:
req.setCharacterEncoding("UTF-8");也可以配置一个全局过滤器来统一设置请求编码,避免每个Servlet重复写。IDEA中设置JSP文件本身编码的位置在Settings -> Editor -> File Encodings,全部设置为UTF-8。
如果你发现这三步全做了还是乱码,那多半是Tomcat的server.xml里Connector没有设置URIEncoding,找到conf/server.xml中的Connector标签,加上URIEncoding="UTF-8"。
6.2 Tomcat启动端口被占用,Tomcat图标正常但页面404
端口被占用很常见,比如之前装的某个服务默认也用了8080。解决办法两种:一是把占用端口的进程找出来杀掉;二是在IDEA的Run Configuration里把Tomcat的HTTP port改动,比如改成8081。
改完后还要注意Deployment里的Application context是否和访问路径对得上——项目部署成功但页面404,原因多半是URL写错了或者Application context路径没有匹配。
另外,IDEA里Tomcat运行配置中还有个Active标签页的After launch设置,勾选后启动完自动打开浏览器,省得每次手动输入URL。
6.3 依赖冲突:重复的Servlet API引起的LinkageError
如果你把pom.xml里Servlet API的scope从provided改成了compile,或者通过其他方式引入了额外的Servlet jar包,启动时可能会报java.lang.LinkageError或者ClassCastException。
原因很好理解:Tomcat自带了Servlet API实现,而你项目的WEB-INF/lib里又多了一份,两个不同版本的类同时存在,JVM加载顺序一乱,就冲突了。
排查方法:在IDEA右侧Maven工具窗口里执行dependency:tree,查看Servlet API是否被多次引入。如果是,将scope改回provided即可。
6.4 JSP页面里的Java代码报错,突然变成红色
如果JSP里嵌入的Java代码在IDEA里大量标红,但项目能正常启动运行——这个情况通常不是代码问题,而是IDEA对JSP语法检查的误报。常见引起原因是JSP页面顶部的指令或标签库引入写得不对,比如<%@ taglib ... %>写错,或者page指令的编码设置异常。
这时候改代码没用,先把File -> Invalidate Caches / Restart执行一次,让IDEA重新索引项目,大部分误报就消失了。这是处理"IDEA工具层面的假报错"最实用的一招。
还有一种情况是Java代码真的报错,那问题多半是你没有在Project Structure -> Modules -> Dependencies检查Servlet API是否正确关联到模块。Maven自动导入之后一般没问题,但如果你手动调整过项目的依赖顺序,有可能把provided依赖的作用范围弄丢了。
7. 一个实战练习:用JSP实现简易用户列表,把请求、业务、视图分离
7.1 需求设定和分层思路
到这里基础都过了一遍,我们用一个小例子把整个项目的思路串起来。需求是这样:页面上显示一个用户列表,数据是模拟的,不需要连数据库。
为了让你理解标准JavaWeb项目的分层思路,我们做三层结构:
- Servlet层:接收请求,调用"业务逻辑",然后把结果放进请求作用域,转发到JSP页面;
- 模型层(Model):用一个JavaBean类
User表示用户实体; - 视图层(View):JSP页面只负责展示数据。
这样的分层就是MVC的雏形,也是后期过渡到Spring MVC的桥梁。你现在用JSP搭出来的项目结构,直接映射到Spring MVC里几乎不用多想——Controller就是Servlet的升级版,Service就是业务逻辑层,View就是视图模板。
7.2 三个关键文件的具体代码和运行效果
先创建一个User.java,放在com.demo.model包下:
package com.demo.model; public class User { private String name; private int age; public User() {} public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }再创建一个UserListServlet.java,放在com.demo.servlet包下:
package com.demo.servlet; import com.demo.model.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; @WebServlet("/userList") public class UserListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<User> users = new ArrayList<>(); users.add(new User("张三", 21)); users.add(new User("李四", 22)); users.add(new User("王五", 23)); req.setAttribute("users", users); req.getRequestDispatcher("/userList.jsp").forward(req, resp); } }最后在webapp下创建userList.jsp:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <title>用户列表</title> </head> <body> <h2>用户列表</h2> <table border="1"> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> </tr> <c:forEach items="${users}" var="user" varStatus="status"> <tr> <td>${status.index + 1}</td> <td>${user.name}</td> <td>${user.age}</td> </tr> </c:forEach> </table> </body> </html>重启Tomcat,访问http://localhost:8080/jsp-demo/userList,你会看到一张带三条数据的表格。
这一步跑通,意味着你已经掌握了JSP开发的完整闭环:Servlet接收请求 -> 构建数据 -> 设置属性 -> 转发JSP -> JSP用EL表达式和JSTL取出数据 -> 渲染页面。这个流程和现在主流Web框架的核心思想是一脉相承的——只是框架帮你省去了大量重复劳动。
7.3 为什么要在JSP页面里用JSTL而不是Java代码
这个练习里我故意用了c:forEach而不是脚本片段循环,目的就是想让你直观感受到两者的差别。
如果写成脚本片段,代码会是这种画风:
<% List<User> users = (List<User>) request.getAttribute("users"); for (int i = 0; i < users.size(); i++) { User u = users.get(i); %> <tr><td><%= i+1 %></td><td><%= u.getName() %></td><td><%= u.getAge() %></td></tr> <% } %>逻辑没毛病,但页面里跟HTML混合着Java代码,看两天你就会疯。而JSTL版本只是一行<c:forEach items="${users}" var="user">,读起来像自然语言,维护性高得多。更重要的一点是,EL表达式和JSTL在设计时就考虑了安全性,像${user.name}这种输出会由JSP容器统一转义处理,比脚本片段裸输出更安全。
8. 部署上线:把项目打成WAR包部署到远程Tomcat
8.1 本地打包时pom.xml需要的插件配置
开发调试完了,项目要真正给别人用,通常是打成WAR包部署到服务器上的Tomcat。这就要在pom.xml里配置war打包插件,同时确保<packaging>war</packaging>。
一个基础的打包配置:
<packaging>war</packaging> <build> <finalName>jsp-demo</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> </plugin> </plugins> </build>finalName决定了打包出来的WAR文件名字,比如jsp-demo.war。如果你的项目中存在测试代码,建议跳过测试打包,命令是mvn package -DskipTests。
8.2 WAR包部署到Tomcat的两种方式
方式一:把WAR文件放到webapps目录下
将target目录下打出的jsp-demo.war拷贝到Tomcat的webapps目录下,然后启动Tomcat。Tomcat启动时发现WAR包,会自动解压并部署。访问URL就是http://服务器IP:8080/jsp-demo/。
方式二:通过Tomcat Manager应用热部署
Tomcat自带的manager应用可以让你在网页上上传WAR包实现热部署。这种方式适合频繁更新的场景,但是需要你提前在conf/tomcat-users.xml里配置manager角色的用户,不然没有权限访问管理界面。
8.3 服务器部署的几个注意点
第一,端口放行:云服务器上如果安全组没有放行8080端口,你本地访问不到但服务器上Tomcat明明已经启动了,这种"远程不可达"问题十个人有九个会忽略防火墙配置。
第二,日志查看:WAR包部署后启动报错,去Tomcat的logs目录下看localhost.log和catalina.out。catalina.out里通常有完整的异常堆栈,根据异常信息定位问题比瞎猜强一万倍。
第三,数据库连接:如果项目连了数据库,注意数据库地址、用户名、密码一定不要硬编码在JSP页面里,应该放到配置文件中,比如src/main/resources下的db.properties,读取时用ClassLoader加载。这是项目上线后最重要的安全意识之一。
9. 我做了这么久JavaWeb项目,最后想对你说的话
每次看到网上有人争论"JSP是不是该淘汰了",我都不太想站队。因为我自己的成长路线就是从JSP起步,逐步理解Servlet、Filter、Listener,然后又顺着这些基础去掌握Spring、Spring Boot。JSP这个技术本身可能越来越边缘化,但它背后包含的Servlet生命周期、请求处理流程、作用域的数据流转、视图层的渲染逻辑,这些概念框架到今天依然没有过时。
如果你现在只是为了交作业、应付考试,那你按着这篇文章走一遍,搭个能跑的项目肯定没问题。但如果你愿意多花一个小时,把每一步后面的原理看清楚——比如为什么Tomcat能运行JSP、为什么web.xml要有、为什么EL表达式能取值——那你后面学Spring MVC、学任何Java后端框架,都会觉得眼熟到不行。
我个人在实际操作中的一段经验是:初期不要迷信网上所谓"最新技术替换旧技术"的言论。新技术永远学不完,但底层原理是有限的。把JSP项目从创建到部署完完整整走一遍,你在"命令行打包""服务器日志查看""端口排错"这些环节积累的手感,会跟着你走很远。
最后分享一个小技巧:如果你后续要接手别人的JSP老项目,别急着全部重写。先把项目里的Servlet和页面请求链路画出来,看清每个JSP是被谁转发的、每个Servlet映射了什么URL,再决定动哪里。多数老项目的问题集中在页面里塞了太多业务逻辑,而不是JSP本身不好用。带着地图去改代码,比上来就是一把梭安全得多。
如果你的项目在创建或部署过程中卡住了,把你遇到的完整错误日志贴出来——记住,是完整错误日志,不是"报错了"三个字。具体到第几行抛了什么异常,我们排查起来效率会高很多。