资讯动态

JSP+Servlet+JavaBean:从混乱页面到规范MVC项目实战

发布时间:2026/10/8 9:53:15 来源:尧图企业网站定制
学 Java Web 学到 JSP 这块的时候很多人会遇到一个特别真实的瓶颈JSP 标签和内置对象都认得能写登录注册能把数据库数据放到表格里但自己攒出来那个项目打开 JSP 文件一看里面全是 Java 代码。这一章第7章的标题叫“JavaBean、Servlet 与 MVC——从 JSP 页面开发走向规范项目”翻译成人话就是别再让 JSP 一个人干所有活了。接下来要做的是把 JavaBean 当作数据模型让 Servlet 负责接收请求和调度让 JSP 只管显示再用 MVC 这条线把它们串成一个能扩展、能维护、能交给别人接手的小项目。如果你最近正在做基于 JSP 的毕设比如论文管理过程系统、学生选课系统之类的可能已经感受到页面一多就改不动了。这篇文章正好把“规范项目”这条路完整讲一遍包含概念原理、最小可运行 Demo 和常见的坑适合已经会基本 JSP/Servlet 语法、但还没做过分层项目的同学。我会从为什么纯 JSP 会失控开始把 JavaBean 的规范、Servlet 的请求调度逻辑、MVC 的分层方式以及一个基于 Maven 的完整项目案例一次说透。1. 先摸清病根纯 JSP 开发为什么会越来越痛苦1.1 早期 JSP 页面最常见的写法很多教材和培训课为了让学生快速看到效果都会先教这种“脚本式 JSP”页面里直接导入 JDBC 相关类直接在% %里写数据库查询再用% %把结果输出到表格中。代码大概长这样% page importjava.sql.* % % Class.forName(com.mysql.cj.jdbc.Driver); Connection conn DriverManager.getConnection(jdbc:mysql://localhost:3306/demo, root, 123456); Statement st conn.createStatement(); ResultSet rs st.executeQuery(select id, name from user); % table % while (rs.next()) { % trtd% rs.getInt(id) %/tdtd% rs.getString(name) %/td/tr % } % /table % rs.close(); conn.close(); %这段代码放在学习阶段完全能跑但它把数据库连接、SQL 语句、HTML 渲染全部塞进了一个文件。页面一旦多起来比如你要做毕业设计里的用户管理、论文上传、成绩录入、公告发布每个模块都复制一份类似代码问题就来了。1.2 失控的三个典型症状第一个症状是改需求难。客户或者老师说“列表里把学号列去掉换成邮箱”你必须在每一个写了这段 SQL 的 JSP 页面里翻一遍。如果某个页面漏改了最后上线就是数据字段对不上而且这种错误往往不是编译期报出来的是用户访问时才发现的。第二个症状是分工难。前端同学想帮你调一下页面样式看到满屏的out.println和% %根本没法下手你写 Java 的人又整天被 CSS 和 HTML 结构折磨。第三个症状是调试难。JSP 是第一次被访问时才编译成 Servlet 的运行时报错的信息里夹杂着一大堆 HTML控制台报错行号和你写的 JSP 行号还不一定对得上。三个症状叠加在一起项目就会变成“能跑但没人敢动”的状态。1.3 规范项目到底规范在哪里所以我理解的“从 JSP 走向规范项目”不是要求你一上来就上 Spring Boot而是先做到这几件事包结构里有清晰的 controller、service、dao、bean 分层JSP 页面里不再出现% %scriptlet只出现 EL 表达式、JSTL 标签和普通的 HTML每个类都能说清楚自己的职责谁接请求、谁算业务、谁存数据、谁渲染页面模块之间靠方法调用而不是复制粘贴。做到这四点项目才算具备“能扩展、能维护、能换人接手”的基本素质。接下来的 JavaBean、Servlet、MVC 三件套正是为了达成这些目标而存在的。2. JavaBean从“数据容器”规范到内省机制2.1 JavaBean 到底是一套什么规范在很多初学者看来JavaBean 就是个普通类。这个理解方向没错但它不够准确。JavaBean 首先是普通 Java 类但只有满足一定纪律的类才敢叫 JavaBean这套纪律包括几项硬性要求类必须有 public 无参数构造器因为你不知道框架或反射机制会用什么方式创建它属性要私有通过 getter/setter 暴露访问入口getter/setter 的命名必须遵守setXxx、getXxx这样的规则boolean 类型写成isXxx为了将来可能的序列化传输和持久化最好实现java.io.Serializable。这些要求单独看都不难难的是每一处命名都保持一致。2.2 一个标准 User Bean 长什么样下面是一个最规范的 User Bean我建议初学者把它当成模板默写出来package com.example.bean; import java.io.Serializable; public class User implements Serializable { private Integer id; private String username; private String password; private String email; public User() { } public Integer getId() { return id; } public void setId(Integer id) { this.id id; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } }这里需要特别强调一点属性名和方法名的对应关系要严格。你写一个getUserName()属性又写成userName虽然大部分手写场景不报错但很多框架工具在反射时找不到属性就会出奇怪的问题。这种命名规范不是洁癖是兼容性。2.3 POJO、Entity、DTO 和 JavaBean 的关系很多同学学到后面会被 POJO、Entity、DTO、VO 这些名字绕晕其实它们之间没有鸿沟。为了让你一眼看清我整理了一张对照表名称含义与 JavaBean 的关系POJO平凡的 Java 对象不强求任何约束JavaBean 是一种更严格的 POJOEntity和数据库表字段一一映射的模型通常就是持久层的 JavaBeanDTO跨层传输的数据对象也是 JavaBean但字段不一定和表对应VO视图层专门组装展示数据的对象也是 JavaBean按页面需求设计字段所以你可以把 JavaBean 理解成“一套通用的数据模型语法”。你在分层项目里写的 Entity、DTO、VO本质上全部是用 JavaBean 的语法写的只是使用场景不同。这个认知对后面理解 Spring MVC 的参数绑定非常重要。2.4 内省机制框架为什么能“看懂”表单JavaBean 规范能流行最大的功臣是 Java 的内省机制。所谓内省就是 JDK 提供了一套工具可以通过类名、方法名反推出这个 Bean 有哪些属性。基于内省Apache Commons BeanUtils 提供了很强大的方法比如把一个 HTTP 请求的参数映射直接塞进一个对象User user new User(); BeanUtils.populate(user, request.getParameterMap());这段代码的执行逻辑是request.getParameterMap()返回表单里所有字段名和值的 MapBeanUtils.populate遍历这个 Map每一个字段名都去User类里找对应的 setter找到就调用。只要表单里的name属性和User的属性同名一行代码就完成了表单绑定。这就是 JavaBean 规范的价值因为大家都统一遵守 getter/setter 命名工具才能写出完全通用的绑定逻辑。如果每个人命名都随心所欲这种通用代码就永远写不出来。2.5 写 Bean 时最容易踩的坑第一个坑是 boolean 类型的命名。假设你有一个属性boolean married规范推荐 getter 写作isMarried()这本身没问题。但不同框架在做 JSON 序列化时有的会把isMarried解析成married有的解析成isMarried前端拿到两种字段名都有可能。第二个坑是往 Bean 里塞业务逻辑。有人图方便把校验规则、格式化方法都写进 Bean短项目无所谓项目一长 Bean 会变成“上帝类”。Bean 只装数据计算和校验放到 Service 层。第三个坑是不写无参构造器。你自定义了一个带三个参数的构造器又没显式写无参构造很多序列化框架默认调用无参构造创建对象运行时就直接抛 InstantiationException。3. ServletJava Web 世界里真正的“入口”3.1 Servlet 和 JSP 的关系一句话版本JSP 本质上就是一个被容器翻译成 Servlet 的模板文件。你在浏览器访问一个 JSPTomcat 第一次访问时会把它编译成_jsp.java再编译成 class 去执行。所以“用 JSP”和“用 Servlet”不是两套路线而是同一套路线的两种形态。JSP 适合写视图Servlet 适合写控制逻辑。Servlet 能接收 HTTP 请求能读请求参数能决定转发到哪个 JSP又能把计算结果塞进 request 或 session 交给视图层。在 MVC 里它天生就是控制器角色。3.2 Servlet 的生命周期理清一次就够了Servlet 的生命周期可以用四个阶段概括init()在 Servlet 创建时执行一次适合做初始化工作service()每次请求都会经过容器会根据 HTTP 方法把请求分发给doGet或doPostdoGet和doPost里写具体处理逻辑但规范项目里这里只做调度不写 SQLdestroy()在容器卸载或应用停止时执行一次用来释放资源。你可以想象餐厅前台的迎宾员客人来了service 负责迎上去问“请问吃中餐还是西餐”然后引导客人到对应座位这个“引导到座位”的动作就是转发到 JSP。一个 Servlet 实例在 Tomcat 中默认只有一个多个请求会并发进入 service 方法所以不要在 Servlet 里用实例变量保存用户状态否则会出现用户串号问题。3.3 用 web.xml 还是用 WebServlet 注解早期写 Servlet 必须在 web.xml 里声明servlet和servlet-mappingServlet 3.0 之后可以直接用注解WebServlet(/register) public class RegisterServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 业务处理 } }两者在功能上等价注解更简洁web.xml 更适合集中管理 URL 映射。小 Demo 随便用正式项目我建议要么全注解要么全 xml不要混着来否则后来人查配置要翻两个地方。还要记住一个规则一个 URL 只能映射一个 Servlet。如果你写了两个相同路径的WebServletTomcat 启动时直接报映射冲突根本起不来。3.4 转发与重定向MVC 里的关键岔路这个知识点再怎么强调都不过分因为它是 MVC 里最容易出错的点。转发和重定向的差异如下维度forward 请求转发redirect 重定向核心方法request.getRequestDispatcher(/xxx.jsp).forward(req, resp)response.sendRedirect(xxx.jsp)浏览器地址栏不变化变成目标地址请求次数服务端内部跳转一次请求客户端再次发请求两次请求request 数据可以继续传递会丢失只能用 URL 参数或 session典型场景控制器查询完数据后渲染 JSP注册/登录成功后外跳、防重复提交实际开发中需要把 JavaBean 数据带到页面上显示优先用 forward。需要防止用户刷新页面导致重复提交表单或者跳转到另一个应用就用 redirect。补充一句sendRedirect如果直接写相对路径容易因为上下文路径问题 404forward 的路径必须以/开头表示当前应用根路径这个细节经常被忽略。3.5 两个绕不开的老问题字符编码和资源路径中文乱码是整个 Java Web 领域问了十几年的问题标准解法其实就三行放在 Servlet 的 doGet/doPost 开头request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8);JSP 页面再配合% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %。Tomcat 8 之后 GET 请求乱码好很多但 POST 提交还是依赖 request 编码设置所以上面三行不能省。资源路径是新手 404 的重灾区。应用部署后浏览器访问的路径会带上上下文路径 ContextPath而且它不一定是/。在 JSP 里取上下文路径标准写法是%request.getContextPath()%/register更优雅的是用 EL 表达式${pageContext.request.contextPath}/register。图片、CSS、JS 的引用同理必须写成${pageContext.request.contextPath}/static/logo.png这样项目部署在任何上下文名下面都不会 404。网上经常有人搜“jsp 图片如何对坐标定位”其实第一个要解决的不是坐标而是图片能不能显示出来。路径不对后面 CSS 的position和坐标全是空谈。4. MVC 拆分从“什么都干的 JSP”到“三个人各干一件事”4.1 三个角色的职责一句话讲清M 是 Model负责数据和业务逻辑在 Java Web 里通常就是 JavaBean 加 Service、DaoV 是 View负责展示JSP 里只用 EL、JSTL、HTML/CSS不出现 Java 代码C 是 ControllerServlet 负责接收请求、读取参数、调用 Model、决定 View 跳转。检验一个项目是否真的拆对了有几个很直观的标准控制器里不应该出现 SQLJSP 里不应该出现new UserDao()这类代码JavaBean 里不应该出现 response 输出。任何一个位置越界就是结构坏掉的信号。4.2 一次注册流程的完整调用链把理论落到代码里看一次注册流程通常是这样走的浏览器提交表单请求到达/register容器调用 RegisterServlet 的 doPost 方法Servlet 用 BeanUtils.populate 把请求参数填充到一个 User 对象Servlet 调用 UserService.register(user)Service 负责校验以后调用 UserDao 保存保存成功后 Servlet 通过 forward 把请求转发到 success.jsp同时把 User 放进 requestsuccess.jsp 用 EL 显示欢迎${user.username}。核心代码大概是这样WebServlet(/register) public class RegisterServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); User user new User(); user.setUsername(request.getParameter(username)); user.setPassword(request.getParameter(password)); user.setEmail(request.getParameter(email)); if (userService.register(user)) { request.setAttribute(user, user); request.getRequestDispatcher(/success.jsp).forward(request, response); } else { request.setAttribute(error, 用户名已存在); request.getRequestDispatcher(/register.jsp).forward(request, response); } } }success.jsp 只需要做一件事% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 % html body h3注册成功/h3 p欢迎${user.username}/p /body /html这里用 forward 而不是 redirect 是刻意的因为要带上 request 里的 user 数据给 JSP 展示。如果用 redirectrequest 里的 user 直接就丢了。4.3 为什么这样拆就能协作和测试拆分带来最直接的好处是并行协作。前端可以只改 JSPJava 工程师不用每天去解 HTML 的乱麻后端接口只要定了前后端可以同时开工。其次是定位 bug页面显示错了查 View参数丢了查 Controller数据显示不对查 Model排查范围一下子缩小到一层。再就是可测试性Service 方法不依赖 Tomcat 也能单独测Servlet 不好测所以我们才强调要把 Servlet 写成“薄控制器”所有业务逻辑下沉到 Service。最后是可扩展性将来把 JSP 换成 Thymeleaf或者把 Servlet 换成 Spring MVC只需要替换一层其他层不用动。4.4 手写 MVC 和 Spring MVC 到底是什么关系以后学 Spring MVC 时你会发现核心思想完全一脉相承。Spring 的 DispatcherServlet 还是那个控制器入口Controller加RequestMapping只是把“一个 URL 映射一个 Servlet”的方式换成了“方法级路由”。你如果能现在把 Servlet JavaBean JSP 的 MVC 完整跑通后面理解 Spring MVC 会特别顺因为说的是同一套语言请求映射、参数绑定、视图解析、转发重定向。很多人直接去学“用 IDEA 基于 Maven 创建 Spring MVC 项目”时一头雾水就是跳过了这一课脑子里没有“控制器薄一点业务下沉”的概念。5. 给你一个能跑通的最小项目Maven Servlet JSP JavaBean5.1 用 IDEA 创建 Maven Web 工程不管你的 IDEA 是 2026 版本还是 2024 版本创建方式基本一致File - New - Project选择 Maven勾选 Create from archetype选择一个maven-archetype-webapp模板填好 GroupId比如com.example和 ArtifactId比如mvc-demo以后等待 Maven 导入完成补上 src/main/java 和 src/main/resources 目录最后在 pom.xml 里加依赖和打包方式。最小配置如下project xmlnshttp://maven.apache.org/POM/4.0.0 modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdmvc-demo/artifactId version1.0-SNAPSHOT/version packagingwar/packaging properties maven.compiler.source11/maven.compiler.source maven.compiler.target11/maven.compiler.target /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjsp-api/artifactId version2.0/version scopeprovided/scope /dependency /dependencies /project注意 servlet-api 和 jsp-api 的 scope 都设为provided因为 Tomcat 自带这些 jar。如果你打包成 war 放进 Tomcat又把这些 jar 一起带进去反而可能引起类加载冲突。5.2 项目结构与包名设计创建完目录以后最终的工程结构应该是这样mvc-demo ├── pom.xml └── src/main ├── java/com/example │ ├── bean/User.java │ ├── dao/UserDao.java │ ├── service/UserService.java │ └── controller/RegisterServlet.java └── webapp ├── WEB-INF/web.xml ├── register.jsp ├── success.jsp └── index.jsp这个包结构其实就是微型的“Controller、Service、Dao、Bean”四层架构。以后你用 Spring Boot 写项目包名可能变成 controller、service、mapper、entity但职责是完全对应的。你现在把这个结构看清楚后面学框架就是换汤不换药。5.3 核心代码实现UserDao 用一个内存 Map 模拟数据库避免还要装 MySQL 才能跑起来package com.example.dao; import com.example.bean.User; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class UserDao { private static final MapString, User DB new ConcurrentHashMap(); public boolean save(User user) { if (DB.containsKey(user.getUsername())) { return false; } DB.put(user.getUsername(), user); return true; } public User findByUsername(String username) { return DB.get(username); } }UserService 负责校验和业务判断package com.example.service; import com.example.bean.User; import com.example.dao.UserDao; public class UserService { private UserDao userDao new UserDao(); public boolean register(User user) { if (user.getUsername() null || user.getUsername().trim().isEmpty()) { return false; } return userDao.save(user); } }RegisterServlet 就是第 4 节里那一段代码。把这个最小项目跑通以后你可以自己再加一个 LoginServlet把查询用户、比较密码、登录成功跳转完整做一遍。多做一遍整个流程就焊死在脑袋里了。5.4 配置 Tomcat 并运行的细节IDEA 里配置 Tomcat 路径是 Run - Edit Configurations - 加一个 Tomcat Server - Local。Deployment 页签里把mvc-demo:war exploded加进去Application context 可以设成/mvc-demo也可以直接设成/。设成/以后访问http://localhost:8080/register.jsp最方便但 JSP 里的资源引用依然建议用${pageContext.request.contextPath}前缀因为你部署到别的环境时上下文不一定是根路径。5.5 运行时的典型问题排查如我在实际调试过程中反复见到的问题整理成一张简单的排查表会比到处翻帖子高效得多现象常见原因解决思路Tomcat 8080 端口被占用某个进程占了端口改 server.xml 或者杀掉占用进程请求报 404URL 少了 ContextPath表单 action 改成${pageContext.request.contextPath}/register参数取出来是 nullinput 的 name 和 getParameter 的 key 不一致打开浏览器 F12 看提交的参数名中文乱码编码设置缺失或文件编码不一致配置 request/response 编码并保证 JSP 文件本身是 UTF-8图片显示不出来img 的 src 没有加 contextPathF12 看实际请求 URL改成${pageContext.request.contextPath}/images/xxx.png再补一句和图片坐标定位相关的内容JSP 页面上想对图片做坐标定位要么用 CSS 的背景图background-position控制位置要么用map标签配合area定义可点击区域这跟 JSP 本身没什么关系就是标准 HTML 能力。但前提仍然是图片的访问路径必须正确否则坐标全白搭。6. 进阶从手写 MVC 到 Spring MVC 的平滑迁移6.1 Spring MVC 中三个角色的映射等到进入框架阶段你手写的这些能力并不会浪费而是会换个样子继续存在。我把映射关系列出来看一眼就明白手写 MVCSpring MVCServlet 类加 WebServletController 类加 RequestMapping 方法request.getParameter / BeanUtilsRequestParam、对象参数自动绑定RequestDispatcher.forwardreturn 视图名或 ModelAndViewsendRedirectreturn redirect:/路径JSP ELJSP EL或 Thymeleaf 等模板6.2 迁移过程中最大的变化是什么最大的区别不是概念而是控制器粒度。手写 MVC 时一个 Servlet 通常对应一个业务动作比如 RegisterServlet 只管注册Spring MVC 里一个 UserController 类可以同时承载/user/register、/user/login、/user/list很多方法路由更细但相关代码更聚合。你如果想具体模拟“某个方法特殊路由指定”Spring MVC 里就是RequestMapping(value /user/{id}, method RequestMethod.GET)这种写法本质还是“把 URL 映射到方法”。这个思路从 Servlet 时代就是一样的只是写法变简洁了。6.3 建议你练熟再走在正式进入 Spring MVC 之前我希望你至少亲手练熟四件事第一练一练 forward 和 redirect 在不同业务里如何选择尤其是带数据和不带数据的差异第二练一练用 BeanUtils.populate 做参数绑定并故意写错属性名看看报什么错第三练一练把 JSP 里的 scriptlet 全部替换成 EL 加 JSTL直到 JSP 里看不到一行%第四练一练写一个过滤器统一处理请求编码这相当于给后期 Spring 的 CharacterEncodingFilter 做预习。最后借这个机会多说一句。很多人找 servlet demo 时只看到零散的单个 Servlet 代码看不到整个目录结构的概念所以每次写完一个功能下个功能又从零开始。我强烈建议你现在就按第 5 节的目录结构在 IDEA 里新建一个 Maven Web 项目自己敲一遍 controller、service、dao、bean 四层再把注册流程完整跑通。等你真正经历过一次“JSP 只剩 HTMLServlet 只剩转发Bean 只剩数据”的状态再回头看那些堆满 Java 代码的 JSP 页面你会发现规范项目这四个字不再是一个抽象口号而是自己手里实实在在写出来的结构。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑