
简介这是一份面向JavaWeb初学者与课程实践者的完整入门项目源码采用JSP、JavaBean与Servlet搭建简易MVC架构实现登录注册、留言板与分页展示等常见功能适合用于课堂作业、课程设计或框架入门练手。压缩包共78个文件约1.86MB包含14个java源文件与14个class编译文件、14个jsp页面、10个css样式表以及xml配置、properties属性文件、jar依赖和一份sql建库脚本覆盖模型层读写数据库、视图层展示信息、控制层衔接前后端的完整结构。资源已有1613人学习下载读者可从中理清Servlet请求分发、JavaBean封装数据与JSP渲染的协作流程并参考分页逻辑与数据库连接配置快速搭建可运行环境、对照调试与二次修改是理解传统JavaWeb开发模式的实用样例。1. 登录注册加留言板一套能跑通的 JavaWeb 三层架构长什么样很多人学 JavaWeb 卡在一个尴尬位置Servlet 生命周期背得滚瓜烂熟JSP 九大内置对象也能默写可真让搭一个带登录、注册、留言的完整小系统就不知道从哪下手了。这套 jspjavabeanservlet 实现的登录注册留言板正好补上这个断层——它不是玩具 Demo而是一个把 MVC 三层架构、会话管理、JDBC 持久化、表单校验串起来的可运行骨架。适合刚学完 Servlet 想找个完整项目练手的人也适合需要快速搭后台管理雏形的从业者。下面按「结构怎么分、代码怎么写、坑在哪」的顺序拆开讲每一步都能直接抄。2. 三层架构落地从目录结构到一次请求的完整链路2.1 为什么这套组合至今仍值得拆JSP JavaBean Servlet 是 JavaWeb 的经典三件套虽然现在前后端分离大行其道但这套组合把 HTTP 请求响应模型、会话状态管理、服务端页面渲染三件事讲得最透。它的分工很清晰Servlet 当控制器负责收请求、调逻辑、转发页面JavaBean 当模型既承载数据User、Message 实体也封装业务DAO 层JSP 当视图只负责展示不写业务逻辑。这种职责切分就是 MVC 的雏形理解了它后面转 Spring MVC 只是换个容器的事。这套资源的价值在于「完整闭环」注册要写库、登录要建会话、留言要关联用户、列表要分页展示四个动作覆盖了 CRUD 的典型场景。比起只写一个 HelloServlet它逼你面对真实问题——密码怎么存、会话怎么防丢、中文怎么不乱码、SQL 注入怎么挡。这些才是面试和实际开发里真正会翻车的地方。2.2 目录结构与依赖清单标准 Maven Web 工程的结构如下照着建不会错src/main/java/ com.demo.entity/ User.java, Message.java com.demo.dao/ UserDao.java, MessageDao.java com.demo.servlet/ LoginServlet, RegisterServlet, MessageServlet com.demo.util/ DBUtil.java src/main/webapp/ login.jsp register.jsp message.jsp list.jsp WEB-INF/web.xml WEB-INF/lib/ mysql-connector-java.jar, jstl.jar依赖只需要两个核心包MySQL 驱动和 JSTL 标签库。如果不用 Maven把 jar 丢进WEB-INF/lib即可用 Maven 则在pom.xml里声明mysql-connector-java和jstl注意 JSTL 要选 1.2 版本1.1 在 Tomcat 8 以上会有兼容问题。数据库建两张表user存 id、username、password、emailmessage存 id、user_id、content、create_time外键关联用户。2.3 一次登录请求的完整链路以登录为例把链路走一遍就明白三层怎么协作。用户在login.jsp提交表单action指向/loginmethod为 postweb.xml或WebServlet把/login映射到LoginServletServlet 的doPost里取参数、调UserDao.findByUsername查库、比对密码、成功则session.setAttribute(user, user)并重定向到留言列表失败则带错误信息转发回登录页。WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 必须在取参数前设置 String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(password)) { req.getSession().setAttribute(user, user); // 会话存用户 resp.sendRedirect(req.getContextPath() /list); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }这段代码有三个关键点setCharacterEncoding必须放在getParameter之前否则中文参数已经按默认编码解析完了再设也没用登录成功用sendRedirect而不是forward避免刷新页面重复提交表单失败用forward保留 request 域里的错误信息。UserDao里用PreparedStatement拼参数别用字符串拼接这是挡 SQL 注入的第一道防线。3. 注册与留言板表单校验、会话拦截与分页查询3.1 注册流程里的校验分层注册比登录多一层校验逻辑要分前端和后端两道。前端在register.jsp用 JS 做非空和格式校验提升体验后端在RegisterServlet里必须再校验一遍因为前端校验可以被绕过。后端校验包括用户名是否已存在查库、密码长度、邮箱格式正则、两次密码是否一致。protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username).trim(); String password req.getParameter(password); String email req.getParameter(email); if (username.isEmpty() || password.length() 6) { req.setAttribute(error, 用户名不能为空密码至少6位); req.getRequestDispatcher(/register.jsp).forward(req, resp); return; } if (userDao.findByUsername(username) ! null) { req.setAttribute(error, 用户名已被占用); req.getRequestDispatcher(/register.jsp).forward(req, resp); return; } userDao.insert(new User(username, password, email)); resp.sendRedirect(req.getContextPath() /login.jsp); }参数说明trim()去掉首尾空格防止用户误输空格导致「看起来一样却登录不上」密码长度校验放在服务端是底线实际项目里还应加盐哈希这套 Demo 为简化用明文生产环境必须改。注册成功后重定向到登录页而不是直接登录是为了让用户确认一次凭据。3.2 留言板的会话拦截与用户关联留言板的核心是「谁发的、发给谁看、怎么展示」。发留言前必须确认已登录做法是在MessageServlet里先取 session 中的 user为空则跳登录页。留言入库时把user_id一起存进去列表查询时用 JOIN 把用户名带出来避免页面显示一串数字 ID。protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); User user (User) req.getSession().getAttribute(user); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } String content req.getParameter(content); if (content ! null !content.trim().isEmpty()) { messageDao.insert(new Message(user.getId(), content.trim())); } resp.sendRedirect(req.getContextPath() /list); }会话拦截这段是血泪经验很多人只在 JSP 页面用c:if判断是否登录但直接访问 Servlet 的 URL 就绕过去了。正确做法是在 Servlet 入口统一拦截或者写一个 Filter 对所有需要登录的路径做检查。留言内容做trim()后判空防止用户提交纯空格。3.3 列表分页与 JSTL 渲染留言多了必须分页否则一次查全表迟早拖垮页面。分页的核心是两个参数pageNo当前页和pageSize每页条数SQL 用LIMIT offset, size。offset (pageNo - 1) * pageSize。public ListMessage findByPage(int pageNo, int pageSize) { int offset (pageNo - 1) * pageSize; String sql SELECT m.*, u.username FROM message m JOIN user u ON m.user_id u.id ORDER BY m.create_time DESC LIMIT ?, ?; // PreparedStatement 设置 offset 和 pageSize }JSP 端用 JSTL 的c:forEach遍历配合c:out输出内容c:out默认转义 HTML能挡住大部分 XSS。分页导航传pageNo时注意边界小于 1 归 1大于总页数归总页数否则LIMIT负数会报错。总页数用COUNT(*)单独查一次别在列表查询里算。4. 避坑排查中文乱码、连接泄漏与重复提交4.1 中文乱码现象是问号根因在编码链现象注册时输入中文用户名存进库变成???或者页面显示乱码。原因有三处可能JSP 页面没声明pageEncodingUTF-8Servlet 取参数前没setCharacterEncoding数据库连接 URL 没带characterEncodingutf8。解决要三处一起改JSP 头部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 第一行设编码JDBC URL 写成jdbc:mysql://localhost:3306/db?useUnicodetruecharacterEncodingutf8。少一处都可能翻车。4.2 连接泄漏现象是跑一会儿就卡死现象系统用一段时间后所有请求都卡住重启 Tomcat 才恢复。原因通常是 DAO 里Connection、PreparedStatement、ResultSet没关连接池耗尽。解决每个资源操作放在try-with-resources里或者finally块中按 ResultSet → Statement → Connection 的逆序关闭。这套 Demo 如果没用连接池每次DriverManager.getConnection开销也大建议至少引入一个轻量连接池。4.3 重复提交现象是刷新页面多一条留言现象用户发完留言按 F5 刷新留言重复入库。原因是表单提交后用了forward浏览器地址栏还停在 POST 请求上。解决提交成功后一律sendRedirect到列表页PRG 模式Post-Redirect-Get让浏览器发起新的 GET 请求刷新就不会重复提交。4.4 会话丢失现象是登录后跳转又变未登录现象登录成功跳转后下一个页面又提示未登录。原因可能是sendRedirect后跨了上下文路径cookie 的 path 不对或者浏览器禁用了 cookiesession 靠 URL 重写又没配。解决重定向时用req.getContextPath()拼完整路径排查时打开浏览器开发者工具看JSESSIONID有没有正常带上。4.5 SQL 注入现象是输入 or 11就能登录现象登录框输入 or 11绕过密码。原因是 DAO 里用字符串拼接 SQL。解决全部改用PreparedStatement的?占位符参数用setString设置。这是最不该犯的错但新手项目里出现频率极高。5. 进阶技巧用 Filter 统一编码与登录校验写到这儿登录、注册、留言的主链路已经能跑通。但每个 Servlet 都写一遍setCharacterEncoding和登录判断重复且容易漏。更工程化的做法是抽一个 Filter把编码设置和会话校验统一前置。WebFilter(/*) public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String uri request.getRequestURI(); // 登录、注册及静态资源放行 if (uri.endsWith(login.jsp) || uri.endsWith(/login) || uri.endsWith(register.jsp) || uri.endsWith(/register)) { chain.doFilter(req, resp); return; } if (request.getSession().getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }这个 Filter 做了两件事统一编码避免每个 Servlet 重复写统一登录拦截白名单放行登录注册相关路径其余一律要求会话里有 user。参数上注意WebFilter(/*)拦截所有请求白名单用endsWith匹配要小心路径后缀误判生产环境建议用精确路径集合。加了 Filter 后前面 Servlet 里的编码和登录判断可以删掉代码立刻干净一截。验证方法很简单清空浏览器 cookie 直接访问/list应该被弹回登录页登录后再访问正常提交一条中文留言库里和页面都不乱码。这三步过了说明 Filter 生效且没误伤。从那以后我每次搭这类小系统都先把 Filter 和try-with-resources这两件事做掉再写业务代码——后悔药不好买前置拦截和资源关闭是最便宜的两颗。希望帮到你。本文还有配套的精品资源点击获取