☰
JSP与Servlet实战分工:从环境配置到用户管理系统开发
2026/10/7 16:55:41 网站建设 项目流程

1. JSP到底是个什么角色,为什么学了Servlet还要学它

先说一个不少初学者心里的嘀咕:Servlet不是能输出HTML吗,折腾JSP不是多此一举?这个问题我当年也纠结过,但真正在项目里跑一遍就明白了,Servlet适合干“逻辑活”,JSP适合干“展示活”,两者搭配才是JavaWeb项目的正常形态。

JSP全称是Java Server Pages,本质上是Servlet的简化写法。你把一个.jsp文件扔进Tomcat,容器会把它转译成一个Java类,这个类说到底就是一个Servlet,只是在编写方式上换了个思路——Servlet里你得用out.println("<h1>hello</h1>")把页面代码硬生生拼在Java字符串里,而JSP允许你在HTML标签中间直接穿插Java代码片段。它解决的核心痛点就是视图层代码的可读性和维护性,一个页面长什么样,直接看JSP源码就八九不离十,不用再在那一大坨字符串拼接里数引号。

可以用一个生活化的类比来理解:Servlet像个厨师,负责在后厨把菜配好、炒熟;JSP像个摆盘师傅,负责把菜装进好看的盘子里端给客人。厨师可以顺便摆个简单盘,但要是每道菜都让厨师精细雕花,后厨就乱套了。项目里的分工就是Controller和Service用Servlet或者框架处理业务,JSP只负责把结果数据渲染成页面。

配合上一篇文章(JavaWeb-08 Servlet)的内容理解会更顺:Servlet里你处理请求、调业务方法、把结果塞进request域,然后request.getRequestDispatcher("/xxx.jsp").forward()转发到JSP页面,JSP再从request域里把数据捞出来渲染。这个“Servlet管逻辑、JSP管展示”的分工模式,是理解后面SSM、SpringBoot+模板引擎这些技术的基础。

JSP的学习门槛并不高,核心就三块:指令、脚本元素、内置对象。把这三块搞明白了,再补几个实际开发天天用的标签库(JSTL和EL表达式),你就能写出一个像样的JSP页面了。这篇笔记我按自己当时的学习路径来写,重点放在“能直接上手用”的部分,一些偏门冷知识点到为止。

2. IDEA里跑JavaWeb项目的那些破事,先搞定环境再谈语法

这一节其实是实打实的血泪经验。很多人跟着教程写代码兴致勃勃,结果卡在环境配置上,页面死活出不来,又不知道去哪看日志。先把IDEA里跑JavaWeb项目的整套流程捋顺,下面写代码才不慌。

2.1 Tomcat版本和IDEA版本的匹配问题

先说版本坑。IDEA Ultimate(旗舰版)才有原生的JavaWeb项目创建向导,社区版没有直接创建Web项目的选项,需要手动配置,不少同学下载的是社区版,一上来就懵了。解决方式有两种:要么换用旗舰版,要么用Maven骨架创建一个maven-archetype-webapp项目,后者即使是社区版也能操作。

更隐蔽的是IDEA内置的Tomcat集成和Tomcat本身的版本兼容问题。比如JDK 17配Tomcat 11,或者老项目用JDK 8却硬要跑Tomcat 10,就会出现各种ClassNotFound、Servlet注解扫不到的情况。推荐一套稳妥组合:JDK 8 + Tomcat 8.5/9.0 + IDEA 2023或更新版本。Tomcat 8.5和9.0在javax.servlet包路径上一脉相承,教程里常见代码不用改包名就能跑起来。Tomcat 10开始把javax.servlet换成jakarta.servlet,很多老代码直接迁移会报错,没必要一上来就给自己上难度。

2.2 IDEA里配置Tomcat的完整步骤

这里把操作路径写细一点,照着点就行:

  1. 下载Tomcat压缩包,解压到一个没有中文和空格的路径,比如D:\dev\apache-tomcat-9.0.85。放在中文路径下偶尔会有资源加载乱码的问题,不是必然,但没必要赌。
  2. IDEA中打开Run菜单 -> Edit Configurations -> 左上角加号 -> Tomcat Server -> Local,注意不要选Tomcat Server -> Remote,那个是远程调试用的。
  3. 在Application server右侧点Configure,选到Tomcat解压目录;JRE选择你当前项目的JDK;HTTP port默认8080,如果被占用可以改成8081。
  4. Deployment(部署)标签页是关键:点加号 -> Artifact,把xxx:war exploded添加进去,Application context填/或者你的项目名,比如/javaweb。war exploded是解压部署模式,开发时热部署更快,不需要打成war包再扔进去。
  5. 回到Server标签页,把On 'Update' action和On frame deactivation都选成Update classes and resources,这样改JSP和静态资源后IDEA会自动同步,不用频繁重启Tomcat。

配置完之后,点右上角的绿色三角启动,浏览器访问http://localhost:8080/你的上下文路径/,能跳出Tomcat首页或者你的测试页面,环境就算通了。

2.3 端口被占用和控制台乱码的快速处理

端口被占用是高频问题。Tomcat启动后控制台直接报Port 8080 was already in use,处理方式很直接:打开命令行执行netstat -ano | findstr 8080,找到占用端口的PID,然后到任务管理器里看清楚是什么进程。如果是残留的Java进程,直接结束它;如果不知道是什么东西,也可以直接把Tomcat端口换成8081,改一下IDEA的HTTP port就行。

控制台中文乱码的问题则源于IDEA默认编码和Tomcat日志编码不一致。Tomcat 9的日志输出用UTF-8的话,在Help -> Edit Custom VM Options里加上-Dfile.encoding=UTF-8,同时把conf\logging.properties里那几个java.util.logging.ConsoleHandler.encoding从UTF-8改成GBK(Windows环境),一般就能解决。这两个动作做完还乱的话,检查一下IDEA右下角文件编码是不是UTF-8,JSP文件里的pageEncoding也要和IDE编码保持一致。

3. JSP核心语法与个人信息展示页实战

环境通了,下面进入正文。JSP页面看起来像HTML,但多了三种东西:指令(Directive)、脚本元素(Scripting)、动作(Action)。实际开发中动作标签用得比较少,重点掌握前两个,再熟练运用JSTL和EL表达式,基本就能应付绝大多数开发场景。

3.1 三个指令的用途,别再都往页面顶部塞了

JSP指令用于“吩咐”容器在转译时做一些额外处理,格式是<%@ 指令名 属性=值 %>。常见的有page、include、taglib三个指令,实际开发中90%的页面只需要page和taglib。

  • page指令:让页面支持Java代码,设置编码,导入需要用到的Java类。比如<%@ page contentType="text/html;charset=UTF-8" language="java" %>是保证页面显示中文不乱码的基础;如果你要在页面里写Java代码操作List等其他类,就得用<%@ page import="java.util.List" %>。
  • include指令:把另一个文件的内容静态包含进来。比如一个后台系统的多个页面都有相同的顶部导航栏,就可以把导航栏抽成一个header.jsp,用<%@ include file="header.jsp" %>引入。注意它是静态包含,发生在转译期,相当于把被包含文件的源码原样嵌进来。
  • taglib指令:引入标签库,最典型的就是JSTL:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>。prefix是标签的前缀,用的时候就是<c:forEach>这样。

注意:page指令中import属性如果用到多个类,用逗号分隔写成一行就行,不要拆成多条page指令,风格上大家默认这样写。

有一个容易混淆的点是<%-- JSP注释 --%>和<!-- HTML注释 -->的区别。JSP注释在转译阶段就被剔除了,用户查看网页源代码完全看不到;HTML注释会随页面输出到浏览器,用户右键查看源码就能看到。所以涉及后端逻辑说明的注释用JSP注释,前端调试用的注释才用HTML注释。

3.2 JSP脚本元素,三种写法分别用在哪

JSP脚本元素就是页面里跟Java相关的代码片段,一共三种形态:

第一种:JSP表达式<%= 表达式 %>,等价于out.print(表达式),用于直接输出一个值。比如<%= request.getAttribute("username") %>。它的特点是不能加分号结尾,只能放一个表达式,别把多条语句塞进去。

第二种:JSP小脚本<% Java语句 %>,这里面写的就是普通的Java代码块,可以写多行,可以写循环、判断等逻辑。比如:

<% User user = (User) request.getAttribute("user"); if (user != null) { out.println("<h2>" + user.getName() + "</h2>"); } %>

第三种:JSP声明<%! 方法或变量 %>,声明的是成员变量和方法。这个在实际开发中尽量少用,因为JSP本质是Servlet,成员变量的生命周期远长于一次请求,极易引发并发数据混乱。记住一句话:在JSP页面里声明成员变量属于给自己埋雷的操作,能用request域和局部变量解决的事,就不要声明成员变量。

这里要补充一个很多初学者容易误解的经典问题:<% %>和<%= %>的区别到底在哪?简而言之,小脚本里你写什么Java代码都行,哪怕是一条赋值语句;表达式里就只能是一个可以被out.print()接收的值。比如<% int a = 1; %>是对的,但<%= int a = 1; %>一定编译不过。

3.3 JSP九大内置对象,先记住这几个就够用了

内置对象意味着你不用声明,直接在页面里拿来就用。这九个是:request、response、session、application、out、page、pageContext、config、exception。实际写页面最常用的就前五个,逐个说清楚:

  • request:一个请求来了,所有参数和信息都在这个对象里。取参数用request.getParameter("name"),取Servlet转发进来的数据用request.getAttribute("xxx")。注意getParameter拿到的是客户端提交的原始参数,getAttribute拿到的是服务端set进去的对象,两者别搞混。
  • response:给客户端回响应。页面里直接用的场景不多,主要是重定向response.sendRedirect("index.jsp")和设置响应头response.setContentType("text/html")。
  • session:会话对象,从浏览器打开到关闭这一段期间的共享数据。典型场景是登录后把用户对象塞进session,其他页面就能直接判断session.getAttribute("loginUser") != null来识别登录状态。
  • application:全局对象,所有用户共享同一个application。适合存全局配置信息,比如网站名、版本号,但别往里面放跟具体用户相关的数据。
  • out:向页面输出内容的对象,表达式<%= %>底层的输出方式就是它。

什么时候用request域、什么时候用session域是实际开发里非常常见的判断题。一个原则是:只在一次请求转发链路上需要的数据,优先用request域;需要在多个请求之间维持的数据,才考虑session域。比如个人信息展示,Servlet查完数据转发给JSP,那数据放request域就够了,别画蛇添足往session里塞,session里对象多了内存压力大,还容易造成数据残留问题。

3.4 实操:做一个JSP个人信息展示页面

下面我按照实际项目里最常遇到的场景,写一个完整的个人信息展示页面。假设已经登录,用户对象存在session里,页面要展示昵称、头像、简介,还要根据用户类型显示不同的操作按钮。

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>个人主页</title> </head> <body> <c:if test="${sessionScope.loginUser != null}"> <div class="profile-card"> <h2>${sessionScope.loginUser.nickname}</h2> <p>简介:${sessionScope.loginUser.bio}</p> <p>注册时间:<fmt:formatDate value="${sessionScope.loginUser.createTime}" pattern="yyyy-MM-dd" /></p> <c:choose> <c:when test="${sessionScope.loginUser.vipLevel == 1}"> <span>VIP用户</span> </c:when> <c:otherwise> <span>普通用户</span> </c:otherwise> </c:choose> </div> </c:if> <c:if test="${sessionScope.loginUser == null}"> <p>你还没有登录,<a href="login.jsp">去登录</a></p> </c:if> </body> </html>

这段代码里有几个值得留意的细节。EL表达式写法${sessionScope.loginUser.nickname}是标准做法,它会自动从session域取loginUser对象并调用其getter方法,比写<%=((User)session.getAttribute("loginUser")).getNickname()%>简直优雅太多了。页面里凡是涉及从域对象取数据输出的地方,优先用EL而不是Java小脚本。

另一个细节是${sessionScope.loginUser != null}这个判断。EL里访问不存在的属性不会抛异常,会乖乖返回null,所以这里可以直接判空。如果用传统的Java小脚本,你得先User u = (User) session.getAttribute("loginUser");再判空,一旦忘了强转或者引用了不存在的key,页面直接给你一大片异常堆栈。

3.5 JSTL和EL表达式,把页面里的Java代码赶出去

很多学过JSP的人后来转向模板引擎(如Thymeleaf),但JSTL配合EL这套组合,在JavaWeb原生生态里依然是统治级的存在。为什么?

EL(Expression Language)解决的是数据怎么取的问题。${user.name}、${sessionScope.loginUser.vipLevel}、${param.id},这些写法取代了request.getParameter、session.getAttribute之类的冗长调用。它还能直接访问集合元素:${list[0].title}、${map['key']}。

JSTL(JSP Standard Tag Library)解决的是页面逻辑怎么写的问题。比较常见的是core核心库里的几个标签:

  • <c:if test="${条件}">:条件判断,没有else,需要配合<c:choose>实现分支。
  • <c:choose> / <c:when> / <c:otherwise>:相当于Java里的switch-case。
  • <c:forEach items="${list}" var="item" varStatus="st">:遍历集合,varStatus还能拿当前循环的下标st.index和是否是第一条st.first。

我见过很多同学在JSP页面里写这样的代码:<% for (int i = 0; i < list.size(); i++) { %>,然后HTML和<% } %>混在一起,页面逻辑一复杂,引号、括号配对的错误找半天。用<c:forEach>之后,遍历一个列表就三行标签的事,页面干净太多。

JSTL本身不是Tomcat内置的,需要额外引入两个jar包:jstl.jar和standard.jar。用Maven项目的话,在pom.xml里加依赖即可。如果是传统方式,把它们扔到WEB-INF/lib目录下。漏掉这一步的典型报错是页面出现Unable to find taglib [http://java.sun.com/jsp/jstl/core],比较容易排查。

4. JSP页面里几个高频实战问题:自动刷新、图片定位和部署路径

这一节专门聊我常见到的新人提问,也是热搜词里高密度出现的问题。这些问题不解决,页面效果就总是差一口气。

4.1 页面加载完后自动刷新一次,这个需求怎么实现

场景很典型:某些数据页面在用户访问时可能还没准备好,或者页面需要在加载完成后从后端拉一遍最新数据,比如一个排队进度页面。热搜词里就有“jsp页面让加载完后刷新一次”,说明这是真实高频需求。

第一种方法,前端实现,在JSP里加一个meta标签,页面加载后几秒自动重刷:

<meta http-equiv="refresh" content="5; url=currentPage.jsp">

意思是5秒后跳转到currentPage.jsp。如果url不写,就是原地刷新。这种方式最常见,但有个坑——如果当前页面本来就是由Servlet转发过来的,刷新时请求参数已经丢了,那需要考虑改成重新请求Servlet的地址。

第二种方法,让Servlet承担刷新逻辑,更符合JavaWeb分层思路。在Servlet的doGet里做一次跳转:

response.setHeader("Refresh", "5; URL=" + request.getContextPath() + "/user/list");

这个响应头告诉浏览器5秒后自动访问/user/list这个地址。好处是URL干净,刷新后的请求又重新走了一遍Servlet,业务逻辑不会丢。

第三种方法,后端控制是否刷新。某些场景只有满足特定条件才需要自动刷新,比如数据还没准备好时刷新,已准备好就直接展示。那就在Servlet里判断后决定要不要加这个响应头。灵活度最高。

注意:不管哪种方式,刷新之后如果页面内容依赖POST提交的数据,建议把表单提交改成Post/Redirect/Get模式,否则浏览器刷新时会弹窗提示“确认重新提交表单”,体验很差。

4.2 JSP里的图片坐标定位,别用错了CSS单位

这个问题也经常有人踩坑。网上有不少旧教程教你在JSP页面里用Java代码算坐标,实际上所有坐标定位的问题都应该交给CSS和HTML解决,JSP只需要输出数据即可。

需求一般是这样的:页面上要显示一张地图或者一张产品示意图,需要在特定位置叠加标记点。比如一个门店分布图,要根据门店的经纬度换算成相对于图片的像素坐标来放标记。

先看基础方案,静态图片定位的正确打开方式:

<div style="position: relative; display: inline-block;"> <img src="map.png" alt="地图" style="width: 600px; height: 400px;"> <div style="position: absolute; left: 180px; top: 120px;"> <span class="marker">门店A</span> </div> </div>

核心逻辑就是:父容器position: relative,图片放里面当背景,标记点用position: absolute相对父容器定位。距离单位用px还是百分比取决于业务场景——图片是固定尺寸,用px最直观;图片要自适应缩放,用百分比更稳。

如果是动态数据,比如从数据库查出每个门店的坐标,那JSP配合JSTL循环输出即可。假设后台算好了每个门店相对图片的x和y坐标(单位是px),存进了shop对象的locX和locY字段:

<c:forEach items="${shopList}" var="shop"> <div style="position: absolute; left: ${shop.locX}px; top: ${shop.locY}px;"> <span class="marker">${shop.name}</span> </div> </c:forEach>

这里要注意,EL表达式输出的是纯数字,拼接px单位的时候必须显式写出来,写成left: ${shop.locX}px,不要只写left: ${shop.locX}。后者生成的CSS是无效的,标记会统统堆在左上角。

坐标怎么换算?如果地图图片的展示尺寸和原始尺寸一致,那坐标直接用图片原始尺寸上的点即可。如果图片有缩放,比如CSS里把600px宽的图显示成了300px,那坐标也得按比例缩放,计算公式为:显示坐标 = 原始坐标 × 显示尺寸 / 原始尺寸。这个换算可以放在后台完成,也可以页面里用max-width:100%配合百分比坐标来处理。

4.3 request.getContextPath()为什么每个链接都要带上

初学者经常写这样的链接:

<a href="/user/detail?id=1">详情</a>

然后页面点击进去发现404。原因是你的项目部署context path不一定是/,可能是/javaweb之类的路径。写成绝对路径/user/detail,浏览器会去访问http://localhost:8080/user/detail,而正确地址是http://localhost:8080/javaweb/user/detail。

解决方式,在JSP页面里动态获取项目根路径:

<% String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; %> <base href="<%=basePath%>">

把这段放在<head>里,之后页面里所有相对路径都会自动基于这个base地址解析。一个比较省事的写法是直接在HTML里用EL:

<base href="${pageContext.request.contextPath}/">

${pageContext.request.contextPath}取到的就是context path,相当于/javaweb。有了base之后,写<a href="user/detail?id=1">就不会再丢项目前缀了。

如果是Servlet向JSP转发,JSP里还要注意:request.getContextPath()返回的是部署路径,与文件物理路径不是一回事。用${pageContext.request.contextPath}这个写法是最稳妥的,兼容各种部署环境。

5. 从展示页到完整项目,JSP+MySQL的用户管理系统怎么搭

单看一个JSP页面体现不出它的工程价值,我拿一个常见的“基于JSP的XX管理系统”案例来拆解,这也是热搜词里“javaweb项目完整案例mysql”、“基于jsp的毕业论文管理过程系统”背后的共性需求。不管系统名头多大,骨架都是一个套路:登录认证 + CRUD + 列表分页。学会了这套骨架,换任何业务字段都能套用。

5.1 项目分层与目录结构安排

这里插一句最重要的建议:JSP页面必须放在WEB-INF目录之外还是之内,一直有争论,但原生JavaWeb开发中,真正该被保护的页面如登录后的主页,建议放在WEB-INF下,通过Servlet转发访问。为什么要这样?WEB-INF目录下的资源不能被浏览器直接URL访问,所有请求必须走Servlet转发进去,这样相当于加了一层访问控制。没有被拦截的页面直接暴露在web目录下,别人输入路径就能打开,安全性差很多。

一个典型的管理系统分层如下:

javaweb-demo/ ├── pom.xml ├── src/main/java │ ├── com.demo.entity // 实体类 │ ├── com.demo.dao // 数据访问层,JDBC操作 │ ├── com.demo.service // 业务逻辑层 │ ├── com.demo.servlet // 控制层,继承HttpServlet │ └── com.demo.util // 工具类,如DBUtil、MD5工具 └── src/main/webapp ├── static/css ├── static/js ├── WEB-INF/jsp // JSP页面放这里 └── index.jsp // 入口

很多人学JavaWeb时有一个误区,JDBC代码直接写在Servlet里,一个Servlet又查数据库又做判断还转发页面。代码量少的时候还行,业务一多就完全失控。从开始学就养成Servlet只做“接收参数、调Service、跳转页面”的习惯,后面学框架会顺很多。

5.2 用户列表页:JSTL循环展示MySQL数据

先看列表页,这个页面几乎是所有后台管理系统的原型。Servlet从数据库查出用户列表,放request域后转发到JSP,JSP用<c:forEach>循环渲染成表格。

Servlet关键代码:

@WebServlet("/admin/user/list") public class UserListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { UserService service = new UserService(); List<User> userList = service.listAllUsers(); req.setAttribute("userList", userList); req.getRequestDispatcher("/WEB-INF/jsp/userList.jsp").forward(req, resp); } }

JSP里的表格展示:

<table class="table"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>昵称</th> <th>注册时间</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach items="${userList}" var="u" varStatus="st"> <tr> <td>${st.count}</td> <td>${u.username}</td> <td>${u.nickname}</td> <td><fmt:formatDate value="${u.createTime}" pattern="yyyy-MM-dd HH:mm:ss" /></td> <td> <a href="admin/user/edit?id=${u.id}">编辑</a> <a href="admin/user/delete?id=${u.id}" onclick="return confirm('确认删除?')">删除</a> </td> </tr> </c:forEach> </tbody> </table>

两个亮点值得注意。fn标签处理时间格式用<fmt:formatDate>,它是JSTL的格式化标签库,需要额外引入uri="http://java.sun.com/jsp/jstl/fmt",避免在页面里手动拼字符串格式化日期。另一个是varStatus="st",st.count从1开始计数,比${u.id}这种数据库自增主键更适合做序号列,删除后ID有空洞也不会显示得奇怪。

5.3 分页查询的实现思路,别一查就全表

初学者最容易犯的毛病就是SELECT * FROM user一把梭,数据一多页面直接卡死。分页是管理系统必备技能,这里给一个简易版本的分页实现。

分页要搞定的参数有三个:当前页码pageNum、每页条数pageSize、总记录数total。对应的SQL是:

-- 查询总条数 SELECT COUNT(*) FROM user; -- 查询某一页数据 SELECT * FROM user LIMIT #{offset}, #{pageSize};

其中offset = (pageNum - 1) * pageSize。写JDBC代码时参数用PreparedStatement的?占位符,不要拼接字符串,否则有SQL注入风险。

JSP页面的分页导航条写成这样:

<div class="pagination"> <c:if test="${pageNum > 1}"> <a href="admin/user/list?pageNum=${pageNum-1}&pageSize=${pageSize}">上一页</a> </c:if> <span>第 ${pageNum} / ${totalPages} 页</span> <c:if test="${pageNum < totalPages}"> <a href="admin/user/list?pageNum=${pageNum+1}&pageSize=${pageSize}">下一页</a> </c:if> </div>

分页里最容易踩的坑是:第一页点删除某条数据后跳回列表,结果页码越界。比如总共只有3页,当前在第3页,删除这条后总页数可能变成2页,如果还在第3页查数据,查出来就是空列表。处理方式有两种:删完后跳回pageNum-1页;或者Servlet里判断如果当前页没有数据但总页数大于0,就重定向到最后一页。

5.4 登录拦截的两种基础实现

管理系统里的JSP页面一般不能裸奔,未登录用户不能直接访问。原生JavaWeb里最直观的方式就是用一个Filter做登录拦截,这也是JavaWeb三大组件(Servlet、Filter、Listener)中Filter的经典应用场景。

写一个LoginFilter:

@WebFilter("/*") public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 放行登录相关的路径和静态资源 String uri = request.getRequestURI(); if (uri.contains("/login") || uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".png")) { chain.doFilter(req, resp); return; } // 判断session里有没有登录用户 HttpSession session = request.getSession(); if (session.getAttribute("loginUser") != null) { chain.doFilter(req, resp); } else { // 没登录就跳转到登录页 response.sendRedirect(request.getContextPath() + "/login.jsp"); } } }

这段代码里有一处新手比较容易忽略的:uri.contains("/login")不能换成uri.endsWith("/login"),因为登录请求可能是/login.jsp,也可能是登录接口/loginServlet,用endsWith("/login")就漏放了。

另外,静态资源不放行会导致一个很隐蔽的体验问题:未登录时跳转到登录页,但登录页的CSS和JS全被拦截了,页面光秃秃的没有样式。这个问题我在练习项目里遇到过好几次,排查思路就是先确认Filter有没有把静态资源访问路径放出去。

5.5 基于JSP的项目还能怎么扩展

原生JSP项目虽然现在主流开发已经不太直接用了,但它作为JavaWeb基础课的地位不会动摇。把这个用户管理系统跑熟练之后,后面往两个方向扩展都顺理成章:

一个方向是引入MVC框架,把Servlet换成SpringMVC类似思想,JSP换成模板渲染。你会发现当初在Servlet里写的那些“取参数-调服务-转发页面”的动作,框架里一个注解就搞定了;JSP里的JSTL+EL那套写法,在模板引擎里也能找到对应的语法糖。

另一个方向是前后端分离。后端只提供JSON接口,前端用Vue、React这类框架渲染数据。这时候原来JSP里渲染表格的工作变成了前端调用接口、拿到JSON后在前端循环生成DOM。Java后端的技术栈重心从“怎么渲染页面”变成“怎么提供接口”。

但我依然建议所有学JavaWeb的人认真把JSP这块啃下来。它让你理解一个最基本的事实:Web应用的本质是请求和响应,页面就是响应的一种呈现形式。理解了这个,后面不管技术栈怎么换,整个请求处理的链路都是相通的。

最后说说我的经验感受。JSP被很多新项目抛弃是事实,但作为技术学习路径的一环,它的价值不是让你以后靠它吃饭,而是帮你建立“视图层和服务端如何配合”的完整认知。我见过不少直接上手SpringBoot的同学,遇到请求转发、域对象、会话跟踪这些问题时一知半解,反过来补JavaWeb基础。先把这个地基打牢,后面框架学起来是事半功倍的。动起手来,把IDEA里的Tomcat跑通,照着这篇笔记写一个用户列表演示出来,比只看不练强一百倍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询