ARTICLE DETAIL

资讯详情

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

Javaweb个人博客实战:Servlet+Bootstrap+AJAX+JSP从零搭建

Javaweb个人博客实战:Servlet+Bootstrap+AJAX+JSP从零搭建 简介这份资源是面向JavaWeb初学者与课程设计学习者的完整项目包围绕Servlet、Bootstrap、Ajax与JSP技术栈实现一个功能简洁的个人博客系统适合正在准备课程设计或希望串联前后端知识点的开发者参考。压缩包共2个文件包含1个zip项目源码包与1个sql数据库脚本整体约4.12MB源码包内按标准JavaWeb目录组织数据库脚本可直接导入以还原博客文章、用户与评论等基础数据。项目覆盖Servlet请求处理、JSP视图渲染、Ajax无刷新评论、Bootstrap响应式页面以及JDBC数据库操作等核心环节并体现MVC分层与session用户认证思路便于读者理解各技术在实际场景中的协作方式。目前已有282人学习可作为课程设计参考或JavaWeb入门练手素材帮助快速搭建可运行的博客原型并梳理项目结构。1. 从零搭一个 Javaweb 个人博客servletbootstrapajaxjsp 到底能做出什么很多人做 Javaweb 课程设计第一反应是去搜「javaweb项目完整案例mysql」结果下下来一堆跑不起来的压缩包Tomcat 一启动就是 404 或者 500。其实用 servlet bootstrap ajax jsp 这套最经典的组合做一个能发文章、能看列表、能登录的个人博客代码量并不大难点在于把请求链路和页面渲染这两件事串明白。这套方案适合两类人一是要交课程设计、需要一套自己能讲清楚原理的完整项目二是刚学完 servlet 想找个东西练手把 JSP 展示、AJAX 异步提交、Bootstrap 排版一次性打通。它不追求高并发也不上 Spring 全家桶就是把最原始的 HTTP 请求响应模型走一遍你做完之后再看任何框架都会觉得眼熟。下面我按实际开发顺序从环境配置讲到分页查询和异步删除把每一步的命令、参数和翻车点都摊开说。2. 环境与项目骨架IDEA 里跑通第一个 servlet 的最小配置2.1 为什么选 Tomcat 9 而不是 10以及 JDK 版本怎么定这套技术栈里最容易出玄学问题的地方就是版本。servlet 的包名在 Tomcat 10 之后从javax.servlet变成了jakarta.servlet如果你照着老教程写import javax.servlet.http.HttpServlet在 Tomcat 10 上编译直接报「程序包 javax.servlet 不存在」。课程设计阶段没必要折腾新命名空间直接锁 Tomcat 9.x JDK 8 或 11网上绝大多数资料都能对上。JDK 选 8 是因为很多学校的实验环境还是 8选 11 也可以但别用 17 以上部分老版本 IDEA 配合 Tomcat 9 会有模块访问警告。在 IDEA 里新建项目时选「Java Enterprise」勾选「Web Application」它会自动生成web目录和WEB-INF/web.xml。如果你用的是社区版 IDEA没有这个选项就建普通 Java 项目手动在项目结构里添加 Web Facet再配 Artifact。手动配的时候注意web目录要放在项目根下WEB-INF里放web.xml和lib编译输出目录设成web/WEB-INF/classes否则 Tomcat 找不到 class 文件。依赖方面只需要两个 jarservlet-api.jar从 Tomcat 的 lib 目录拷或者用 Maven 坐标javax.servlet:javax.servlet-api:4.0.1scope 设 provided和mysql-connector-java8.x 版本对应 MySQL 8。如果你用 Mavenpom.xml里加这两条就够了别引一堆用不上的包课程设计答辩时老师问起来你也不好解释。!-- pom.xml 关键依赖scope 一定要写对 -- dependencies !-- servlet-api 由 Tomcat 提供打包时不带进去否则冲突 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- MySQL 8 驱动注意时区参数在 URL 里配 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies参数说明scope写provided是因为 Tomcat 自带 servlet-api如果你打成 war 包时把它带进去启动会报类加载冲突。MySQL 驱动版本要和你的数据库版本匹配8.0 的库用 5.x 驱动连会报「Unknown system variable query_cache_size」。2.2 数据库建表和连接池的最小写法博客至少需要三张表用户表、文章表、分类表可选。课程设计为了简单可以只做用户和文章两张。建表语句如下注意字符集用utf8mb4否则 emoji 存进去会变问号。-- 用户表存登录信息 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 实际项目要加盐哈希课设可先明文 nickname VARCHAR(50) DEFAULT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 文章表存博客正文 CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, author_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (author_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;连接数据库我一般写一个DBUtil工具类用DriverManager直接拿连接课设够用。如果你想让代码好看点可以引 Druid 或 HikariCP但多一个 jar 就多一个配置项答辩时被问到连接池参数答不上来反而尴尬。DBUtil里把 URL、用户名、密码写成常量URL 里必须带useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8少一个时区参数就可能报「The server time zone value is unrecognized」。// DBUtil.java 最简连接获取 public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/blog?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD 123456; public static Connection getConn() throws SQLException { try { Class.forName(com.mysql.cj.jdbc.Driver); // 8.x 驱动类名带 cj } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PWD); } }逻辑说明Class.forName加载驱动MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver写老版的com.mysql.jdbc.Driver会提示已过时但仍能连。每次getConn都新建连接课设访问量小没问题但记得在 DAO 里用 try-with-resources 关闭否则连接数一多数据库就拒绝服务。3. Servlet 处理请求与 JSP 渲染把文章列表和详情页跑通3.1 一个 Servlet 同时处理列表和详情的写法Servlet 的映射可以用WebServlet注解也可以用web.xml。课设推荐注解少写配置文件。比如文章模块我习惯用一个ArticleServlet通过参数action区分操作list查列表、detail查详情、add发文章、delete删文章。这样比每个功能写一个 Servlet 类要清爽也方便在前端统一请求路径。WebServlet(/article) public class ArticleServlet extends HttpServlet { private ArticleDao articleDao new ArticleDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (list.equals(action)) { // 查所有文章转发到列表页 ListArticle list articleDao.findAll(); req.setAttribute(articles, list); req.getRequestDispatcher(/list.jsp).forward(req, resp); } else if (detail.equals(action)) { // 根据 id 查单篇 String id req.getParameter(id); Article article articleDao.findById(Integer.parseInt(id)); req.setAttribute(article, article); req.getRequestDispatcher(/detail.jsp).forward(req, resp); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置编码必须在取参数之前 req.setCharacterEncoding(UTF-8); String action req.getParameter(action); if (add.equals(action)) { String title req.getParameter(title); String content req.getParameter(content); articleDao.insert(new Article(title, content, 1)); resp.sendRedirect(article?actionlist); // 重定向回列表 } } }参数说明req.setCharacterEncoding(UTF-8)只对 POST 请求体有效GET 请求的中文乱码要在 Tomcat 的server.xml里给 Connector 加URIEncodingUTF-8或者干脆用 POST。forward是服务器内部跳转URL 不变sendRedirect是让浏览器重新请求URL 会变。发完文章用重定向是为了防止刷新页面重复提交。3.2 JSP 里用 JSTL 循环展示文章列表JSP 里不要写 Java 脚本片段% %用 JSTL 和 EL 表达式页面干净老师看着也舒服。需要引入jstl.jar和standard.jar或者 Maven 坐标javax.servlet:jstl:1.2。列表页核心代码% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table classtable table-hover thead trth标题/thth作者/thth时间/thth操作/th/tr /thead tbody c:forEach items${articles} vara tr tda hrefarticle?actiondetailid${a.id}${a.title}/a/td td${a.authorName}/td td${a.createTime}/td tdbutton classbtn btn-danger btn-sm onclickdel(${a.id})删除/button/td /tr /c:forEach /tbody /table逻辑说明c:forEach遍历 request 域里的articles${a.id}是 EL 表达式会自动调用 getter。注意items里的属性名要和req.setAttribute的 key 一致。如果列表为空页面什么都不显示最好加一个c:if test${empty articles}提示「暂无文章」。3.3 Bootstrap 排版用 CDN 还是本地文件Bootstrap 用 5.x 还是 4.x 都行课设推荐 5.x栅格类名和 4 差不多但去掉了 jQuery 依赖。引入方式有两种CDN 和本地。学校机房可能断网建议下载到web/static目录下本地引入答辩演示时不怕网络问题。!-- 在 JSP 的 head 里引入本地 Bootstrap -- link relstylesheet href${pageContext.request.contextPath}/static/css/bootstrap.min.css script src${pageContext.request.contextPath}/static/js/bootstrap.bundle.min.js/script参数说明${pageContext.request.contextPath}拿到项目上下文路径避免项目名变了之后路径 404。Bootstrap 的container、row、col-md-8这套栅格用来做博客布局很顺手左边文章列表占 8 列右边个人信息占 4 列响应式在手机上会自动堆叠。4. AJAX 异步交互删除、登录校验和编码格式的坑4.1 用原生 fetch 发 AJAX 请求删除文章AJAX 的作用是不刷新页面就能删文章、校验用户名。jQuery 的$.ajax用的人多但 Bootstrap 5 已经不依赖 jQuery 了我建议直接用原生fetch代码更短也不用额外引库。// 删除文章不刷新页面 function del(id) { if (!confirm(确定删除)) return; fetch(article?actiondeleteid id, { method: POST }) .then(resp resp.text()) .then(result { if (result ok) { // 删除成功把当前行移除 document.querySelector(button[onclickdel(${id})]).closest(tr).remove(); } else { alert(删除失败); } }) .catch(err console.error(请求出错, err)); }逻辑说明fetch第一个参数是 URL第二个参数是配置对象。这里用 POST 传 idServlet 里doPost处理delete动作删除成功后返回字符串ok。前端拿到ok后用 DOM 操作移除表格行页面不刷新。注意fetch默认不带 cookie如果登录态用 session 存需要加credentials: same-origin。4.2 AJAX 请求参数编码POST 和 GET 的编码差异热搜里有人问「ajax请求设置编码格式」这是真踩过坑才会问的。AJAX 发 POST 请求时如果请求头是application/x-www-form-urlencoded中文参数要在服务端用req.setCharacterEncoding(UTF-8)解码。如果发的是 JSON请求头设Content-Type: application/jsonServlet 里就得用流读取再手动解析课设阶段不推荐直接表单编码最简单。// 登录校验POST 表单编码 fetch(user?actioncheck, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: username encodeURIComponent(name) })参数说明encodeURIComponent对参数值编码防止特殊字符截断。服务端req.getParameter(username)拿到的就是解码后的值。如果发现中文变乱码先检查 Tomcat 的server.xml里 Connector 有没有URIEncodingUTF-8再检查 Servlet 里setCharacterEncoding是不是在getParameter之前调用。4.3 登录拦截用 Filter 还是每个 Servlet 里判断博客的后台发文章、删除文章需要登录才能操作。最省事的做法是写一个LoginFilter拦截/article和/admin/*路径检查 session 里有没有用户。没登录就重定向到登录页。Filter 的WebFilter注解里urlPatterns可以写多个。WebFilter(urlPatterns {/article, /admin/*}) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); } else { chain.doFilter(req, resp); // 放行 } } }逻辑说明chain.doFilter是放行到下一个过滤器或目标 Servlet。注意登录页和登录请求本身不能被拦截否则会死循环。urlPatterns里不要包含/login和/user。5. 避坑与排查课程设计里最容易翻车的 5 个地方5.1 现象Tomcat 启动报 404访问任何页面都是「未找到」原因IDEA 的 Artifact 没配好WEB-INF/classes下没有编译后的 class 文件或者web.xml里metadata-completetrue导致注解不生效。解决打开 Project Structure → Artifacts确认 Output Layout 里WEB-INF/classes指向了编译输出目录web.xml的web-app标签里不要写metadata-completetrue否则WebServlet会被忽略。5.2 现象JSP 页面中文乱码浏览器显示问号原因JSP 文件本身编码不是 UTF-8或者响应头没设charset。解决在 JSP 第一行加% page contentTypetext/html;charsetUTF-8 languagejava %同时 IDEA 的 File Encoding 设成 UTF-8。数据库连接 URL 里加characterEncodingutf8建表用utf8mb4。5.3 现象AJAX 请求返回 200 但前端拿不到数据原因Servlet 返回的是转发到 JSP 的 HTML而 AJAX 期望的是纯文本或 JSON。解决AJAX 处理的 Servlet 分支里不要用forward直接用resp.getWriter().write(ok)输出。如果返回 JSON设resp.setContentType(application/json;charsetUTF-8)手动拼 JSON 字符串或用 Gson。5.4 现象删除文章后刷新页面数据又回来了原因删除操作后用了forward而不是sendRedirect浏览器地址栏还是删除请求的 URL刷新就重复提交。解决增删改操作完成后一律resp.sendRedirect(article?actionlist)让浏览器重新发一次 GET 请求。5.5 现象MySQL 连接报「Public Key Retrieval is not allowed」原因MySQL 8 默认用caching_sha2_password认证插件JDBC 连接时不允许公钥检索。解决在 URL 后面加allowPublicKeyRetrievaltrue或者把用户的认证插件改成mysql_native_password。课设环境直接加参数最快。6. 分页查询与异步加载把博客列表做成能翻页的进阶技巧列表文章一多全查出来页面会很长分页是课程设计里能加分的点。分页的核心是 SQL 的LIMIT和COUNT。先查总数再按页码算偏移量。我一般写一个PageBeanT泛型类装当前页数据、总页数、当前页码。// 分页查询 DAO 写法 public PageBeanArticle findByPage(int pageNo, int pageSize) { PageBeanArticle pb new PageBean(); pb.setPageNo(pageNo); pb.setPageSize(pageSize); // 查总数 String countSql SELECT COUNT(*) FROM article; // 查当前页数据偏移量 (页码-1)*每页条数 String dataSql SELECT * FROM article ORDER BY create_time DESC LIMIT ?, ?; // ... JDBC 执行setInt(1, (pageNo-1)*pageSize), setInt(2, pageSize) return pb; }参数说明LIMIT第一个参数是偏移量从 0 开始第二个参数是每页条数。页码从 1 开始传偏移量就是(pageNo-1)*pageSize。如果页码超过总页数返回空列表前端显示「没有更多了」。前端分页可以用 AJAX 异步加载点「下一页」时不刷新整个页面只替换表格内容。这样体验更接近现代博客。实现思路把文章列表抽成一个list.jsp片段AJAX 请求article?actionlistpageNo2返回这段 HTML前端用innerHTML替换。注意返回的片段不要带html和body标签否则嵌套会乱。// 异步翻页 function loadPage(pageNo) { fetch(article?actionlistpageNo pageNo) .then(resp resp.text()) .then(html { document.getElementById(articleTable).innerHTML html; }); }这里有个血泪经验AJAX 返回的 HTML 片段里如果包含script标签innerHTML插入后脚本不会执行。所以删除按钮的onclick要写在标签属性里或者用事件委托绑在父元素上。我一般用事件委托在articleTable上监听 click判断event.target是不是删除按钮。最后说一个验证方法分页做完后手动在地址栏输pageNo999看是否返回空列表而不是报错再输pageNo0或负数看有没有做边界判断。这两个测试能过分页逻辑基本就没问题。我自己做课设时习惯把每页条数设成 5方便演示翻页效果数据量小也能看出页码变化。希望帮到你。本文还有配套的精品资源点击获取
返回列表