
简介本资源是一份面向JavaWeb初学者与课程设计实践者的完整个人博客系统开发案例聚焦Servlet、Bootstrap、Ajax与JSP四大核心技术的协同应用帮助学习者掌握Web前后端交互、响应式界面开发及动态内容渲染等关键能力。压缩包共2个文件1个ZIP源码包1个SQL数据库脚本总大小4.12MBZIP包内含标准JavaWeb项目结构含WEB-INF、src、lib等目录涵盖用户登录、博客发布、评论提交等核心功能模块SQL脚本用于初始化MySQL数据库表结构与基础数据。已有282人学习下载适合高校JavaWeb课程实训、期末项目参考或自学进阶使用。读者可直接部署运行深入理解MVC分层实现、Session会话管理、EL/JSTL页面表达、Ajax无刷新交互及Bootstrap响应式布局等实战要点同时获得清晰的工程组织范例与可调试的完整代码链路。1. 这不是“又一个JSP页面”而是一套能跑通、能调试、能交作业、还能当简历项目的JavaWeb个人博客骨架你手头正开着IDEA刚建好一个Dynamic Web Project却卡在web.xml里配了三遍servlet-mapping还是404你复制粘贴了一段Bootstrap导航栏代码结果CSS全乱套控制台报bootstrap.min.js:6 Uncaught TypeError: Cannot read property fn of undefined你写完Ajax提交表单后端Servlet明明打了断点但request.getParameter(title)永远是null——这不是玄学是JavaWeb初学者在搭建个人博客时最真实的血泪现场。这个标题里的“servletbootstrapajaxjsp”不是技术堆砌而是一条被验证过的最小可行路径用原生Servlet处理请求与业务逻辑用Bootstrap快速构建响应式UI用Ajax实现无刷新交互比如点赞、评论提交用JSP作为视图层渲染动态内容用户信息、文章列表、分页。它不依赖Spring Boot自动装配不引入Maven复杂依赖适合课程设计验收、毕设开题演示、甚至作为JavaWeb工程师面试时可现场调试的“活体项目”。如果你需要一个能本地启动、数据库可换、代码结构清晰、错误有迹可循的落地方案而不是一堆零散代码片段这篇笔记就是为你写的。2. 从零搭起项目骨架目录结构、依赖配置与Servlet容器选型2.1 目录结构必须遵循Servlet规范否则JSP根本找不到Java类JavaWeb项目不是把.java和.jsp文件随便扔进src或WebContent就能跑。IDEA新建Dynamic Web Project后默认结构常埋雷src下放Servlet类但WEB-INF/classes没被正确映射lib目录空着连javax.servlet-api-4.0.1.jar都没加更致命的是web.xml放在WebContent/WEB-INF/下但IDEA没把它识别为部署描述符。我一般会手动校验三层路径Java源码层src/main/java/com/example/blog/包名按实际改所有Servlet、DAO、Bean类放这里Web资源层src/main/webapp/不是WebContent这是Maven标准即使非Maven项目也建议统一里面放index.jsp、css/、js/、images/、WEB-INF/部署描述符层src/main/webapp/WEB-INF/web.xml且必须声明web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0——版本号错一位比如写成3.1会导致Tomcat 9拒绝加载。提示不要用IDEA默认的“Add Framework Support”勾选Servlet它可能生成过时的web.xml模板。直接删掉旧文件用上面的XML头新建一个。2.2 Tomcat 9.0.83 是当前最稳的Servlet容器选择拒绝Tomcat 10课程设计环境追求稳定压倒一切。Tomcat 10强制升级到Jakarta EE 9规范所有包名从javax.*变成jakarta.*这意味着你抄来的import javax.servlet.http.HttpServlet;会编译失败而网上90%的JSP/Serlvet教程仍基于javax。实测Tomcat 9.0.832023年10月发布对Servlet 4.0、JSP 2.3、EL 3.0支持完美且与Bootstrap 5.3、jQuery 3.6兼容无压力。下载后解压在IDEA中配置Server Runtime Environment时务必指向apache-tomcat-9.0.83根目录而非bin或lib子目录——这是新手最常翻车的一步。2.3 依赖只加3个JARServlet API、JDBC驱动、JSON解析器这个博客不需要Hibernate或MyBatis。精简依赖是避免冲突的关键JAR文件版本作用获取方式javax.servlet-api-4.0.1.jar4.0.1提供HttpServlet、HttpServletRequest等核心类Maven Repository下载不要从Tomcatlib目录复制会引发重复类加载mysql-connector-java-8.0.33.jar8.0.33MySQL 8连接驱动支持serverTimezoneUTC参数官网下载注意8.0版本需用com.mysql.cj.jdbc.Driverjson-simple-1.1.1.jar1.1.1Ajax返回JSON数据比Jackson轻量无反射依赖GitHub Release页下载仅120KB注意这三个JAR必须放在src/main/webapp/WEB-INF/lib/下不是src/lib或项目根目录Tomcat启动时才会自动加入classloader。放错位置会导致ClassNotFoundException且IDEA不会报编译错误——这是静默故障。2.4 创建第一个ServletArticleListServlet验证请求路由是否打通别急着写登录先让首页能显示文章列表。创建com.example.blog.servlet.ArticleListServletpackage com.example.blog.servlet; import com.example.blog.dao.ArticleDao; import com.example.blog.entity.Article; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/articles) public class ArticleListServlet extends HttpServlet { private static final long serialVersionUID 1L; Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 查询数据库 ListArticle articles new ArticleDao().findAll(); // 2. 存入request域供JSP取值 request.setAttribute(articleList, articles); // 3. 转发到JSP页面注意路径从WebRoot开始 request.getRequestDispatcher(/index.jsp).forward(request, response); } }关键点说明WebServlet(/articles)替代web.xml配置更直观但若用Tomcat 8以下必须用web.xml方式request.getRequestDispatcher(/index.jsp)中的/表示Web应用根目录即http://localhost:8080/your-project/index.jspArticleDao和Article实体类需提前写好DAO里用Connection conn DriverManager.getConnection(jdbc:mysql://localhost:3306/blog?serverTimezoneUTC, root, 123456);——serverTimezoneUTC是MySQL 8必加参数否则报时区错误。3. Bootstrap JSP如何让静态页面“活”起来而不是CSS失效的摆设3.1 Bootstrap 5.3 的CDN引入必须带integrity校验否则本地开发会404很多同学直接复制Bootstrap官网CDN!-- 错误没加integrity离线开发时CDN不可达页面白屏 -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.3/dist/css/bootstrap.min.css relstylesheet正确做法是加上SRISubresource Integrity校验!-- 正确本地开发时CDN失效自动回退到本地文件 -- link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.3/dist/css/bootstrap.min.css relstylesheet integritysha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6h4u4vhkR4j/o crossoriginanonymous script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.3/dist/js/bootstrap.bundle.min.js integritysha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz crossoriginanonymous/script提示如果公司内网禁外网CDN把bootstrap.min.css和bootstrap.bundle.min.js下载到src/main/webapp/css/和js/目录改为link hrefcss/bootstrap.min.css relstylesheet——但必须确保文件名和路径完全匹配大小写敏感。3.2 JSP动态渲染文章列表用JSTL替代Scriptlet告别% %灾难别再用% while(rs.next()) { %这种写法。它让HTML和Java混杂调试困难且容易引发NullPointerException。改用JSTLJavaServer Pages Standard Tag Library在web.xml中声明JSTL支持Servlet 4.0必需jsp-config jsp-property-group url-pattern*.jsp/url-pattern el-ignoredfalse/el-ignored page-encodingUTF-8/page-encoding /jsp-property-group /jsp-config在index.jsp顶部引入JSTL核心标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % page contentTypetext/html;charsetUTF-8 languagejava %渲染文章列表Bootstrap Card样式div classrow c:forEach items${articleList} vararticle div classcol-md-6 col-lg-4 mb-4 div classcard h-100 div classcard-body h5 classcard-title${article.title}/h5 p classcard-text text-muted small${article.content.substring(0, 50)}.../p div classd-flex justify-content-between align-items-center span classbadge bg-primary${article.author}/span small classtext-muted${article.createTime}/small /div /div div classcard-footer a hrefarticle-detail.jsp?id${article.id} classbtn btn-sm btn-outline-primary阅读全文/a /div /div /div /c:forEach /div关键点说明${articleList}来自Servlet的request.setAttribute(articleList, articles)substring(0,50)在JSP中安全截取避免StringIndexOutOfBoundsExceptionJSTL会自动处理nullhrefarticle-detail.jsp?id${article.id}生成带参数的URL比JavaScript拼接更可靠。3.3 Bootstrap表单与JSP联动注册页如何把数据交给Servlet一个典型的注册表单register.jspform idregisterForm actionRegisterServlet methodpost div classmb-3 label forusername classform-label用户名/label input typetext classform-control idusername nameusername required /div div classmb-3 label foremail classform-label邮箱/label input typeemail classform-control idemail nameemail required /div div classmb-3 label forpassword classform-label密码/label input typepassword classform-control idpassword namepassword required /div button typesubmit classbtn btn-primary注册/button /form注意三个细节actionRegisterServlet路径是相对于Web应用根目录Servlet类名必须与WebServlet(/RegisterServlet)一致nameusername等属性名必须与Servlet中request.getParameter(username)的字符串完全相同大小写敏感required属性由浏览器做前端校验但后端Servlet必须再次校验防止绕过。4. Ajax请求设置编码格式为什么中文总是乱码以及如何一劳永逸解决4.1 GET请求乱码URL编码必须在前端JS中显式处理当Ajax用GET传中文参数如搜索关键词“JavaWeb”后端request.getParameter(keyword)得到的是JavaWeb乱码。原因浏览器默认用ISO-8859-1编码URL而Tomcat用UTF-8解码。解决方案分两步前端JSsearch.jsfunction doSearch() { const keyword document.getElementById(searchInput).value; // 关键用encodeURIComponent强制UTF-8编码 const encodedKeyword encodeURIComponent(keyword); fetch(/search?keyword${encodedKeyword}) .then(response response.json()) .then(data renderResults(data)); }后端ServletSearchServlet.javaOverride protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 关键对GET参数单独设置编码 String keyword request.getParameter(keyword); if (keyword ! null) { keyword new String(keyword.getBytes(ISO-8859-1), UTF-8); } // 后续业务逻辑... }提示new String(keyword.getBytes(ISO-8859-1), UTF-8)是Tomcat 9下GET乱码的标准解法。不要试图改server.xml的URIEncodingUTF-8——它只对路径有效对查询参数无效。4.2 POST请求乱码Filter统一拦截比每个Servlet写setCharacterEncoding更可靠为避免每个Servlet都写request.setCharacterEncoding(UTF-8)创建EncodingFilterpackage com.example.blog.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 1. 设置请求编码对POST表单有效 request.setCharacterEncoding(UTF-8); // 2. 设置响应编码对JSP输出有效 response.setContentType(text/html;charsetUTF-8); // 3. 继续过滤链 chain.doFilter(request, response); } }在web.xml中注册filter filter-nameEncodingFilter/filter-name filter-classcom.example.blog.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping4.3 Ajax返回JSON用response.getWriter().write(jsonString)而非out.print()很多同学用JSP内置对象out.print()输出JSON结果页面上出现{status:success}一堆HTML乱码。正确做法是在Servlet中直接写响应体Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String title request.getParameter(title); String content request.getParameter(content); // ... 保存逻辑 // 关键设置响应头告诉浏览器这是JSON response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\status\:\success\,\message\:\发布成功\}); }注意response.getWriter()和response.getOutputStream()不能共存调用前者后不能再调用后者否则抛IllegalStateException。5. 避坑指南那些让JavaWeb项目集体翻车的5个真实场景5.1 现象点击Bootstrap按钮没反应控制台报Uncaught ReferenceError: $ is not defined原因jQuery未加载或加载顺序错误。Bootstrap 5已移除jQuery依赖但你的代码里还写了$(#myBtn).click(...)。解决要么降级到Bootstrap 4.6仍需jQuery要么改用原生JSdocument.getElementById(myBtn).addEventListener(click, function(){...})。检查script标签顺序jQuery必须在Bootstrap JS之前且都在/body前。5.2 现象JSP页面显示${article.title}原始字符串而不是文章标题原因EL表达式被禁用。常见于web.xml中设置了el-ignoredtrue/el-ignored或Tomcat版本与Servlet规范不匹配。解决确认web.xml中el-ignoredfalse/el-ignored检查Tomcat版本9.0对应Servlet 4.0支持EL3.0删除JSP开头的% page isELIgnoredtrue %。5.3 现象Ajax请求返回404 Not Found但Servlet类明明存在原因URL路径错误。常见错误包括actionservlet/RegisterServlet多写了servlet/前缀、WebServlet(/RegisterServlet)但访问http://localhost:8080/blog/RegisterServlet漏了项目名blog。解决在浏览器开发者工具Network标签页看实际发出的请求URL是什么对比WebServlet注解路径与浏览器地址栏完整URL用request.getContextPath()动态获取项目名。5.4 现象MySQL连接报错Access denied for user rootlocalhost但命令行能登录原因MySQL 8默认认证插件改为caching_sha2_password而mysql-connector-java-8.0.33需显式指定useSSLfalseserverTimezoneUTC。解决JDBC URL改为jdbc:mysql://localhost:3306/blog?useSSLfalseserverTimezoneUTCallowPublicKeyRetrievaltrue或在MySQL中执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;。5.5 现象Tomcat启动后修改JSP文件不生效必须重启原因IDEA的On Update Action设置为No update或Tomcat的reloadablefalse。解决在IDEA中File → Settings → Build → Compiler → Build project automatically 勾选Tomcat配置中Deployment → Application context 设为/并勾选Deploy at server startup在conf/context.xml中Context reloadabletrue。6. 让博客真正可用的3个进阶技巧分页、图片上传、防XSS攻击6.1 Bootstrap分页组件 Servlet分页逻辑一页10条不查全表前端index.jsp中插入Bootstrap分页HTMLnav aria-label文章分页 ul classpagination justify-content-center c:if test${currentPage 1} li classpage-itema classpage-link href?page${currentPage-1}上一页/a/li /c:if c:forEach begin1 end${totalPages} vari li classpage-item ${i currentPage ? active : } a classpage-link href?page${i}${i}/a /li /c:forEach c:if test${currentPage totalPages} li classpage-itema classpage-link href?page${currentPage1}下一页/a/li /c:if /ul /nav后端ArticleListServlet改造// 获取当前页码默认第1页 int currentPage Integer.parseInt(request.getParameter(page) ! null ? request.getParameter(page) : 1); int pageSize 10; int offset (currentPage - 1) * pageSize; // 查询总记录数用于计算总页数 int totalCount new ArticleDao().countAll(); int totalPages (int) Math.ceil((double) totalCount / pageSize); // 查询当前页数据 ListArticle articles new ArticleDao().findPage(offset, pageSize); request.setAttribute(articleList, articles); request.setAttribute(currentPage, currentPage); request.setAttribute(totalPages, totalPages); request.getRequestDispatcher(/index.jsp).forward(request, response);DAO层SQL示例MySQLSELECT id, title, content, author, create_time FROM article ORDER BY create_time DESC LIMIT ?, ?LIMIT ?, ?中的?对应offset和pageSize防止SQL注入。6.2 JSP图片上传用input typefile ServletPartAPI不依赖第三方库upload.jsp表单form actionUploadServlet methodpost enctypemultipart/form-data div classmb-3 label forarticleImage classform-label封面图片/label input typefile classform-control idarticleImage nameimage acceptimage/* required /div button typesubmit classbtn btn-primary上传/button /formUploadServlet.java处理Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取上传文件Part Part part request.getPart(image); String fileName Paths.get(part.getSubmittedFileName()).getFileName().toString(); // 生成唯一文件名防止覆盖 String uniqueName System.currentTimeMillis() _ fileName; // 保存到服务器目录绝对路径 String uploadPath request.getServletContext().getRealPath(/uploads/); File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); part.write(uploadPath File.separator uniqueName); // 返回JSON结果 response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\status\:\success\,\url\:\/uploads/ uniqueName \}); }注意request.getPart(image)是Servlet 3.0标准API无需Apache Commons FileUploadgetRealPath(/uploads/)返回Web应用下的物理路径确保/uploads/目录存在。6.3 防XSS攻击JSP输出时用c:out value${article.content} escapeXmltrue/用户提交的文章内容可能含scriptalert(xss)/script。若直接${article.content}输出脚本会执行。正确做法div classcard-text c:out value${article.content} escapeXmltrue/ /divescapeXmltrue将转为lt;转为gt;彻底阻断HTML标签解析。这是JSTL提供的安全输出方式比自己写StringEscapeUtils.escapeHtml4()更轻量。最后说句实在话这个博客项目我带过三届学生从建项目到答辩平均耗时12小时。最大的教训是——别在没跑通Servlet跳转前就折腾Bootstrap样式也别在Ajax没返回JSON前就写前端渲染逻辑。把/articles能打开、/RegisterServlet能接收参数、/UploadServlet能存文件这三件事做扎实后面全是填空题。希望帮到你。本文还有配套的精品资源点击获取