ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSP动态网页开发实战:从Model2到Spring Boot集成与常见坑

JSP动态网页开发实战:从Model2到Spring Boot集成与常见坑 拿JSP做毕设或者实训项目是很多学JavaWeb的必经之路。但大部分人的体验是照着教程敲完一个登录注册感觉“会了”一换题目或者一部署到服务器立刻懵。JSP这东西你别把它当成一门多高深的技术它就是一套“在HTML里写Java逻辑”的模板机制核心价值在于帮你快速生成动态网页。但恰恰因为它入门门槛低很多人的底层概念是糊的——请求怎么走的、转发和重定向到底啥区别、为什么页面一多代码就乱成一锅粥。这篇内容我打算换个思路不从语法列表开讲而是围绕“从理论到实战”这条线把JSP动态网页开发的完整链路梳理一遍先搞清楚它运行的本质再解决工程组织问题用一个人人都做过的“注册功能”完整落地Model2思想接着讲讲Spring Boot 2集成JSP时最容易翻车的目录和配置问题最后整理一份排错速查表和一些适合拿来做毕设的选题方向。无论你是准备应付头歌实训、在做JavaWeb课设还是打算用JSP做毕业设计这篇文章里的实践经验都值得你花时间看一遍。1. 理解JSP的本质它不是编程语言而是一种视图模板技术1.1 JSP到底是怎么跑起来的很多教程上来就讲JSP语法但我觉得最该先讲的是“JSP文件被访问时发生了什么”。你要明白JSP最终会被容器比如Tomcat翻译成一个Servlet类然后编译成class文件再走一遍Servlet的生命周期。翻译过来的类里核心方法是_jspService(HttpServletRequest request, HttpServletResponse response)你在JSP里写的所有模板内容和Java脚本最终都被拼接并输出到response中。我举个生活化的例子你去餐厅点菜后厨的厨师Servlet按订单准备菜而JSP就相当于一个带插槽的菜单模板——菜品有什么变化不需要重新印刷菜单只要把插槽里的内容换成新数据就行。这也是为什么JSP适合做数据展示型页面页面骨架固定数据动态变化。第一次访问一个JSP页面时你可能会觉得特别慢这不奇怪。因为容器要先做“翻译、编译、加载、实例化”这一整套动作之后请求就直接走已经编译好的类速度就正常了。你把JSP文件改了再刷新容器通常能通过文件时间戳检测到变化自动重新翻译编译一次这个机制在开发时非常顺手但也带来一个隐患——线上千万别随手改线上JSP文件一旦语法错误整站可能直接报500。1.2 内置对象别死记用场景去理解JSP有九大内置对象request、response、session、application、pageContext、out、config、page、exception。很多人拿来就背背完就忘。我的建议是直接用场景去记。request对象代表一次客户端请求生命周期到响应结束为止session对象代表一个用户会话从你打开浏览器到关闭浏览器如果没超时一直存在application对象代表整个Web应用所有用户共享。这三个是真正的高频选手。pageContext的作用域最小只在当前JSP页面有效通常用来获取其他隐式对象。记住一个判断依据你想存的数据是给“这次请求”用、给“这个用户”用还是给“所有用户”用分别对应request、session、application。比如登录后的用户名你得放session里因为跳转页面后还要用但表单校验的错误提示放request里就够了转发到页面显示完就结束。这样想就不会把作用域搞混了。1.3 页面指令和动作元素是分水岭% page %指令里藏着大量的坑。contentType和pageEncoding两个属性经常被忽略但恰恰是中文乱码的根源。我的经验是在JSP页面第一行指定contentTypetext/html; charsetUTF-8和pageEncodingUTF-8同时确保Tomcat的GET/POST请求编码也统一为UTF-8三者一致后90%的中文乱码问题都能消失。% include %指令和jsp:include动作的区别很多人到了答辩都讲不清楚。简单说指令是“静态包含”在翻译阶段就把文件内容直接复制进来整个页面只生成一个Servlet动作是“动态包含”运行时把请求转发给被包含页面然后合并输出相当于多个Servlet。静态包含适合导航栏、页脚这种几乎不变的结构动态包含适合数据会变化的板块因为每次都能重新执行。你要是做过页面头尾公共部分的抽取这个区别就能体会得很深。2. 工程结构得跟上从Model1到Model2早转型早舒服2.1 Model1模式为什么会被淘汰Model1的典型写法是JSP页面里既写HTML、又写Java脚本片段、又操作数据库。页面顶部十几个% page import %页面中间一堆% ... %表单提交到同一个页面通过request.getParameter()取值再存库。小项目跑起来确实很爽连Servlet都不用建。但问题很快会出现前端改个样式你要在一堆Java代码里翻HTML你写了一个查询逻辑另一个页面想复用只能复制粘贴多个账号协作时改同一个文件容易冲突。我见过太多课程设计代码整个系统就三个JSP文件每个文件一千多行名副其实的“蜘蛛网”。这就是Model1的边界——适合演示不适合真实项目。2.2 Model2思想的核心工作流Model2本质上就是MVC在Web开发中的落地Servlet当控制器Controller负责接收请求、调用业务、决定跳转JavaBean充当模型Model封装数据和业务逻辑JSP只做视图View专注展示。我自己记法很简单请求先找Servlet说“我要干嘛”Servlet去调后台“把数据准备好”最后Servlet带着数据去JSP说“照着这个模板把数据填进去”。做注册功能的流程就是典型Model2注册页面填写表单数据提交给RegistServletRegistServlet去调UserService里对应的业务方法业务层再调DAO层访问数据库完成后Servlet根据结果转发到成功页或失败页。2.3 包结构划分建议我建议哪怕做一个很小的JavaWeb项目也从第一天开始就按下面这个结构分包不要嫌麻烦com.example.entity实体类对应数据库表com.example.dao数据访问层只做数据库读写com.example.service业务逻辑层处理校验、事务等com.example.servlet控制器层接收请求和跳转com.example.util工具类如数据库连接、字符串处理com.example.filter过滤器处理全局编码、登录拦截这样分的好处是以后你从JSP项目迁移到Spring MVC或者Spring Boot你会发现自己的思路完全能平滑过渡Controller对应Servlet、Service还是Service、DAO变成Mapper。有很多人毕业问为什么学完JSP学Spring MVC还是很懵其实就是在分层这块没建立意识。3. 实战用Model2思想完成用户注册功能3.1 需求分析和数据库表设计注册功能看似简单但做完整它涉及了几乎所有JavaWeb基础知识点表单提交、请求参数处理、后端校验、数据库操作、Session、请求转发与重定向。先明确需求用户输入用户名、密码、确认密码、邮箱用户名不能重复密码不能为空且长度至少6位两次密码必须一致邮箱格式必须合法注册成功跳转到登录页失败返回注册页并回显错误信息数据库表设计就一个user表字段别贪多CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(64) NOT NULL, email varchar(100) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里注意两点一是用户名加上唯一索引数据库层面做个兜底防止并发下重复注册二是密码字段长度至少64因为后面要存MD5或者加盐哈希明文密码的存储方式在项目中千万别干。3.2 数据库连接从DriverManager到连接池如果是纯JDBC项目写一个数据库连接工具类很常见核心就是两步加载驱动、获取连接。MySQL 8及以上版本注意驱动类是com.mysql.cj.jdbc.Driver不再是旧的com.mysql.jdbc.DriverURL后面还要加时区参数比如serverTimezoneAsia/Shanghai不然会报时区错误。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/jsp_demo?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD root; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }但说句实在话真实项目中很少直接这么用一般会用Druid或C3P0这样的连接池目的是复用连接、避免频繁创建销毁。做实训或课设时如果老师只要求JDBC那么上面的工具类足够如果想在答辩里加点亮点可以换成Druid连接池并说明连接池的优势非常加分。3.3 DAO层SQL操作就写在这里DAO层只做一件事把Java对象和数据库表记录做映射。用户注册需要两个方法根据用户名查记录、插入一条新记录。public class UserDao { // 根据用户名查询 public User findByUsername(String username) { String sql SELECT id, username, password, email, create_time FROM user WHERE username ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setEmail(rs.getString(email)); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; } // 插入新用户 public boolean addUser(User user) { String sql INSERT INTO user (username, password, email) VALUES (?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, user.getUsername()); ps.setString(2, user.getPassword()); ps.setString(3, user.getEmail()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); } return false; } }为什么要用PreparedStatement而不是Statement拼SQL一个关键原因是防SQL注入。比如用户名输入一个 OR 11如果拿字符串直接拼SQL条件恒真数据就全部暴露了。PreparedStatement用?占位符参数会被当作纯字符串处理输入内容再刁钻也不会破环SQL结构。这也是面试中非常高频的考察点。3.4 Service层业务校验放在这层很多人把校验写在Servlet里这也能跑但会让控制器越来越膨胀。我的习惯是Servlet只管“接收参数 调Service 跳转”具体业务规则都放到Service层。比如注册时Service要做这样几件事非空检查用户名、密码、邮箱都不能为空格式检查邮箱正则匹配密码长度至少6位重复检查先调DAO查用户名若存在则返回提示信息密码处理不能明文入库做MD5加盐哈希执行插入DAO层写入public class UserService { private UserDao userDao new UserDao(); public String register(User user) { // 1. 数据基本校验 if (isBlank(user.getUsername()) || isBlank(user.getPassword())) { return 用户名和密码不能为空; } if (user.getPassword().length() 6) { return 密码长度不能少于6位; } // 2. 用户名重复检查 if (userDao.findByUsername(user.getUsername()) ! null) { return 用户名已存在; } // 3. 密码md5加盐 String salt jsp2025; String hashedPwd MD5Util.md5(salt user.getPassword()); user.setPassword(hashedPwd); // 4. 入库 return userDao.addUser(user) ? success : 注册失败; } }Service层写完后Controller会非常清爽。以后你学Spring的时候事务、依赖注入这些概念就是挂在这一层上的所以提前养成习惯没有坏处。3.5 Servlet控制器转发和重定向到底怎么选注册用的ServletdoGet负责跳转到注册页面doPost负责处理提交。主要逻辑是取参数、封装User对象、调Service、根据返回结果判断。WebServlet(/regist) public class RegistServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher(/regist.jsp).forward(req, resp); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); String email req.getParameter(email); User user new User(); user.setUsername(username); user.setPassword(password); user.setEmail(email); String result userService.register(user); if (success.equals(result)) { // 重定向到登录页避免刷新重复提交 resp.sendRedirect(req.getContextPath() /login.jsp); } else { req.setAttribute(error, result); req.getRequestDispatcher(/regist.jsp).forward(req, resp); } } }登录成功后跳转我强烈建议用sendRedirect而不是forward。区别在哪转发是服务器内部行为浏览器地址栏不变你刷新一下页面表单又提交一次同一条数据可能插两遍重定向是服务器告诉浏览器“你重新请求这个地址”地址栏会变刷新也不会重复提交。这就是Post/Redirect/Get模式是很多人忽略但面试爱问的小技巧。3.6 JSP视图层尽量别写Java脚本片段最后落到注册页面。我见过很多同学的JSP是这样的页面里全是% if(...) { %、% for(...) { %。能跑但可读性一塌糊涂。更推荐的方案是EL表达式加JSTL标签。EL用${}取域对象里的值JSTL用c:if、c:forEach做逻辑控制。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title用户注册/title /head body h2注册新账号/h2 c:if test${not empty error} p stylecolor:red;${error}/p /c:if form action${pageContext.request.contextPath}/regist methodpost p用户名input typetext nameusername value${param.username} //p p密码input typepassword namepassword //p p确认密码input typepassword nameconfirmPwd //p p邮箱input typetext nameemail value${param.email} //p pbutton typesubmit注册/button/p /form /body /html需要说明的是尽管JSP里可以完全不用脚本片段但不同实训平台的要求不一样。像头歌这类平台上的JavaWeb练习经常会让补全某个JSP里的代码片段这些题目考点其实非常集中要么是request.getParameter取参数要么是page指令属性补全要么是转发语句。你如果能把Model2的完整流程彻底理解那些填空题换汤不换药。3.7 头歌实训题目的考点归纳我帮不少学弟学妹看过“头歌实训答案”相关的搜索记录说实话直接搜答案对提升没有任何帮助。这些实训题目的核心考点其实很固定我给你盘一下JSP页面指令pageEncoding、contentType、import的补全请求参数获取request.getParameter(xxx)的写法转发与重定向RequestDispatcher.forward()和sendRedirect()的区别Session操作setAttribute、getAttribute、会话超时设置EL表达式从request/session中取值常见的有${param.username}JSTL标签c:if、c:forEach、c:choose的使用简单JDBC加载驱动、获取连接、PreparedStatement执行增删改查你要是能把一个注册功能从页面到数据库完整走通头歌里的多数JSP关卡都能轻松过。死记答案没有意义面试官问两句就会露馅。4. Spring Boot 2集成JSP目录和配置是最大的坑4.1 为什么Spring Boot官方不推荐JSP到了Spring Boot阶段你会发现官方文档更推荐Thymeleaf、Freemarker这类模板引擎原因主要有三个一是JSP需要Jasper引擎来解析而Spring Boot内嵌Tomcat默认没带二是JSP最终要编译成class无法打包进可执行的JAR包这就限死了部署方式三是前后端分离趋势下模板引擎的定位越来越尴尬。但现实是很多学校的课设题目仍然要求用JSP或者你接手的老项目就是JSP写的所以“Spring Boot 2集成JSP”依然是高频搜索词。既然绕不开那就把关键配置和目录结构吃透踩过坑之后你会发现也没那么可怕。4.2 正确的工程目录结构Spring Boot集成JSP最典型的坑就是目录不对。我见过好几个人把jsp文件放在src/main/java下或者放在resources下结果访问永远404。Spring Boot识别JSP页面的默认位置是src/main/webapp/WEB-INF/jsp/JSP文件放这里src/main/webapp/static/CSS、JS、图片放这里也可以放resources下然后在application.properties里配置视图解析器spring.mvc.view.prefix/WEB-INF/jsp/ spring.mvc.view.suffix.jspController返回字符串时就直接返回逻辑视图名比如返回registSpring Boot会自动拼成/WEB-INF/jsp/regist.jsp。4.3 必须加的两个依赖在pom.xml里除了spring-boot-starter-web还要加上Jasper解析器和JSTL。dependency groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-jasper/artifactId scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependencytomcat-embed-jasper的scope用provided是因为部署到外置Tomcat时Tomcat自带了Jasper避免冲突。如果你只加了依赖但没加这个scope可能会在打包或者运行时碰到一些奇怪的问题。4.4 打包部署老老实实打WARSpring Boot JSP在开发环境用SpringBootApplication直接跑通常没问题但一旦打成JAR用java -jar运行JSP十有八九访问不到。原因我之前说了JSP需要Jasper在运行时编译JAR包内嵌的Web应用目录结构对JSP支持很差。稳妥方案是打WAR包部署到外置Tomcat。改动就三步启动类继承SpringBootServletInitializer并重写configure方法SpringBootApplication public class JspDemoApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(JspDemoApplication.class); } public static void main(String[] args) { SpringApplication.run(JspDemoApplication.class, args); } }设置packaging为warpackagingwar/packaging排除内嵌Tomcatdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency然后把WAR包扔进Tomcat的webapps目录启动后访问就行。这样虽然不能享受Spring Boot一键JAR启动的便利但至少JSP页面能正常编译渲染。5. 常见问题速查与排错技巧做JavaWeb项目最打击人的不是代码写不出来而是各种莫名其妙的环境和配置问题。我把这些年碰到的高频问题整理成一个速查表几乎每一条都有人踩过。现象原因解决方案JSP页面中文乱码页面编码、请求编码、数据库编码不一致统一UTF-8检查pageEncoding、contentType、JDBC URL请求参数中文乱码表单POST提交未指定编码在web.xml配置CharacterEncodingFilter强制UTF-8访问JSP报404JSP文件不在webapp/WEB-INF目录下检查目录结构确认视图解析器配置报错Unable to compile class for JSPJDK版本与Tomcat不支持换用支持的JDK版本或升级TomcatJSTL标签无法使用缺少jstl依赖或Taglib URI写错添加jstl和standard依赖确认URI。Spring Boot项目用tomcat-embed-jasper jstl访问CSS/JS 404静态资源路径写错使用${pageContext.request.contextPath}拼绝对路径不要用相对路径页面改了没生效Tomcat缓存了旧class清除Tomcat的work目录再启动登录后刷新页面重复提交表单POST后直接转发使用sendRedirect方式实现PRG模式Session失效后操作报错会话超时未处理设置合理的session超时写登录拦截filter数据库操作中文乱码JDBC URL缺少characterEncoding参数URL加上characterEncodingutf8排查的时候有个习惯一定要养成先看Tomcat日志也就是catalina.out或者IDEA控制台的完整异常栈。很多人一遇到异常就截图丢群里问“怎么回事”其实堆栈信息里通常第一行就写清楚了。我自己的排错顺序是日志 浏览器开发者工具Network面板 打断点。6. 毕设选题方向与页面展示加分技巧6.1 有含金量的功能点罗列很多人的毕设题目是“某某管理系统”说得好听是系统说白了就是增删改查。题目本身卷不卷不重要重要的是你能不能在里面埋几个能讲清楚的技术点。我见过的优质毕设哪怕功能简单但至少包含下面技术之一分页查询封装一个PageBean讲解limit偏移量计算条件搜索多条件拼接SQL用动态SQL思想文件上传实现头像上传讲解虚拟目录映射用户权限用Filter做登录拦截和角色控制数据统计JSP页面通过ECharts展示统计图表验证码用Kaptcha工具生成图形验证码Excel导出POI操作百万数据导出随便挑一个技术点在答辩时讲清楚“为什么这么设计”比堆五六个CRUD模块有用得多。6.2 个人信息展示页面的设计思路“JSP个人信息展示页面”这个需求听起来简单但要做得像样其实很考验基础能力。我的建议是走“Servlet JSP JDBC”最简流程登录后从Session取用户信息若是首次访问或信息有变更就通过Servlet从数据库重新查询封装到request域再转发到个人信息页面展示。页面布局上用CSS做卡片式设计个人信息每行一个字段字段名左对齐、值右对齐数据为空时给出“未填写”的提示别直接输出null。如果想加点动态感可以放一个基于Canvas的烟花模拟器效果——页面加载时用canvas标签生成烟花粒子动画庆祝用户完成资料填写。注意这里JSP只负责输出初始用户数据比如昵称、城市、注册天数动画逻辑全在JavaScript里画面刷新后烟花重新燃放每次效果都不同展示时视觉冲击力很强。这个项目虽然小但正好覆盖了JSP动态数据输出、Servlet请求处理、前端交互三个层次作为课设完全够用。6.3 分页功能凭什么能当亮点分页是几乎所有管理系统的刚需也是我最推荐在毕设中重点讲的一个点。核心思路很简单页面收到pageNum和pageSize两个参数Servlet算好总记录数和总页数从数据库用LIMIT offset, pageSize查出当前页数据连同书本数据一起塞进PageBean对象转发给JSP用c:forEach渲染底部页码用c:if判断上一页下一页的可用性。分页其实带出了两个关键知识点一是SQL的LIMIT参数绑定用PreparedStatement来写不要字符串拼接二是我们常说的“OFFSET分页在数据量大时性能差”的原因——数据库必须扫过offset之前的行才能返回后面的数据。你可以顺带提一下基于游标的分页优化答辩时这就是加分项。6.4 毕设避坑建议最后给做毕设的同学一些过来人的建议。第一别一上来就追新求大什么Spring Cloud、微服务、消息队列和你一个JSP毕设根本不搭老师一眼就知道你是从网上抄的。你不如把一个注册登录、分页查询讲得清清楚楚至少每行代码都能解释。第二别拿一个现成的模板系统换个皮就交差答辩随便一问“你那个查询条件是怎么拼接的”就卡壳了。第三一定把自己的项目跑通后再写论文别说“设计并实现了一个系统”结果现场演示时页面都打不开那是最尴尬的情况。我个人一直觉得毕设的本质不是要求你做出多惊天动地的系统而是让你通过一个完整项目把大学里学过的知识点串起来。JSP这套技术虽然老但它把请求-处理-响应的链路摊得非常清楚你把它吃透了后面学Spring MVC、Spring Boot会轻松非常多。到现在我面试新人如果对方说自己学过JavaWeb我还是会习惯问一下JSP九大内置对象和转发重定向的区别这两个问题足以分辨一个人是真写过项目还是只是刷过题。
返回列表