JavaWeb实战:从零搭建小型音乐网站,掌握Servlet、JSP与MVC全链路开发

📅 发布时间:2026/9/3 1:27:21
JavaWeb实战:从零搭建小型音乐网站,掌握Servlet、JSP与MVC全链路开发 简介这是一套基于JavaWeb技术栈开发的小型音乐网站完整项目面向Java初学者与Web开发入门者解决在线音乐播放、下载、分类浏览及排行榜展示等核心功能实现问题。资源包共881个文件涵盖80个JSP页面、60个Java后端类、44个运行依赖JAR包、23首MP3示例音频、以及大量前端资源116个JPG、222个PNG、22个JS、18个HTML、16个CSS结构清晰模块划分明确便于理解MVC分层设计与前后端交互逻辑。压缩包大小为189.75MB已内置可直接部署的WAR包、完整MySQL建表SQL脚本及全部配置文件XML、properties等支持EclipseTomcatMySQLJDK环境一键运行与二次开发。目前已有912人学习下载读者可立即获得可运行的全功能Demo、规范的工程目录结构、真实音频资源集成方案及常见Web异常排错参考是实践Servlet/JSP、数据库连接池、文件上传下载与页面动态渲染的理想学习样本。1. 项目缘起一个“麻雀虽小五脏俱全”的练手项目最近在带几个刚学完JavaWeb基础的朋友做项目发现他们普遍有个困惑学了一大堆Servlet、JSP、JDBC、MVC的理论但真到动手时面对一个空白的IDEA项目却不知道从何下手感觉知识点都是散的串不起来。他们需要一个能把这些技术点都“用起来”的、有完整流程的、但又不过于复杂的项目来练手。于是我决定带着他们一起从零开始搭建一个“小型音乐网站”。这个项目的目标非常明确不求功能多炫酷但求流程完整、技术栈典型、能跑通从数据库到前端的全链路。它就像一个技术“骨架”把JavaWeb的核心技术Servlet、JSP、JDBC、MVC、Filter、Listener等像拼图一样严丝合缝地组装起来。做完这个项目你就能清晰地理解一个Web应用是如何接收请求、处理业务、访问数据、最终渲染页面的这对于建立完整的开发认知至关重要。这个项目非常适合以下人群刚刚学完JavaWeb基础想找一个完整案例巩固的在校学生准备面试需要项目经验增加谈资的求职者或者任何想从“看教程”过渡到“自己写”的入门开发者。我们使用的技术栈是经典的“JSP Servlet JavaBean”模式数据库选用最普遍的MySQL开发工具是IntelliJ IDEA这些都是企业里最常见的组合学会了可以直接迁移到更复杂的框架如Spring MVC学习中。2. 环境搭建与项目初始化从“新建项目”到“跑通Hello World”万事开头难一个清晰的项目初始环境能避免后续80%的诡异错误。我们使用IntelliJ IDEA Ultimate版社区版对Web项目支持稍弱作为开发工具。2.1 创建Web项目与配置Tomcat打开IDEA选择File - New - Project。左侧选择Java Enterprise确保已勾选Web Application并选择正确的JDK版本推荐JDK 8或11兼容性最好。项目创建后IDEA会自动生成一个标准的Web项目结构src目录放Java代码web目录或webapp下会有WEB-INF和index.jsp。接下来是核心一步配置本地Tomcat服务器。点击IDEA右上角的Add Configuration...点击号选择Tomcat Server - Local。在Deployment标签页点击号选择Artifact添加你的项目war包。这里有个关键点Application context应用上下文最好设置为/或者/music这决定了你访问项目的根路径。例如设为/music那么你的首页访问地址就是http://localhost:8080/music/。配置完成后点击运行如果能在浏览器看到默认的index.jsp页面说明环境基本就绪。注意很多新手会卡在“404”错误上。请务必检查1. Tomcat的Deployment中是否成功添加了你的项目Artifact2. 项目的web.xml中是否配置了欢迎页面welcome-file-list3. 访问的URL路径是否正确。2.2 数据库设计与建表我们的音乐网站功能很简单用户能浏览音乐列表、根据歌名或歌手搜索、播放音乐模拟。因此数据库只需要一张核心表。我们在MySQL中创建一个名为music_db的数据库然后执行以下SQL建表语句CREATE TABLE t_music ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键, title varchar(100) NOT NULL COMMENT 歌曲名称, singer varchar(50) NOT NULL COMMENT 歌手, album varchar(100) DEFAULT NULL COMMENT 专辑, release_time date DEFAULT NULL COMMENT 发行时间, music_time varchar(10) DEFAULT NULL COMMENT 时长如04:30, music_url varchar(255) NOT NULL COMMENT 音乐文件存储路径相对路径, cover_url varchar(255) DEFAULT NULL COMMENT 封面图片存储路径, play_count int(11) DEFAULT 0 COMMENT 播放次数, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT音乐表;这里有几个设计考量id使用自增主键是标准做法。music_url和cover_url存储的是相对服务器路径例如/music/audio/xxx.mp3和/music/images/xxx.jpg。绝对路径不利于项目迁移。play_count默认为0用于后续做简单的“热门歌曲”统计。字符集使用utf8mb4以支持存储Emoji等特殊字符避免乱码。建完表后可以插入几条测试数据INSERT INTO t_music (title, singer, album, release_time, music_time, music_url, cover_url) VALUES (起风了, 买辣椒也用券, 起风了, 2018-05-30, 04:25, /music/audio/起风了.mp3, /music/images/起风了.jpg), (孤勇者, 陈奕迅, 《英雄联盟双城之战》动画剧集主题曲, 2021-11-08, 04:16, /music/audio/孤勇者.mp3, /music/images/孤勇者.jpg), (晴天, 周杰伦, 叶惠美, 2003-07-31, 04:29, /music/audio/晴天.mp3, /music/images/晴天.jpg);2.3 项目依赖管理与目录结构规划在WEB-INF下创建lib文件夹用于存放项目所需的JAR包。对于这个项目我们至少需要MySQL驱动包 (mysql-connector-java-8.0.xx.jar)用于连接数据库。JSTL标签库包 (jstl.jar和standard.jar)为了在JSP中避免使用% %脚本我们使用JSTL和EL表达式让页面更清晰。druid-1.2.xx.jar这是一个阿里巴巴开源的数据库连接池。强烈建议使用连接池而不是每次都创建新连接它能显著提升性能和管理连接资源。将上述JAR包复制到lib目录后需要让IDEA识别它们。右键点击lib文件夹选择Add as Library...。至此一个干净、标准的JavaWeb项目骨架就搭建完成了。我们的目录结构看起来应该是这样的music-website-project ├── src │ └── (后续的Servlet、JavaBean等包) ├── web │ ├── WEB-INF │ │ ├── lib │ │ │ ├── mysql-connector-java-8.0.xx.jar │ │ │ ├── druid-1.2.xx.jar │ │ │ └── jstl.jar、standard.jar │ │ └── web.xml │ ├── index.jsp │ ├── audio (文件夹存放音乐文件) │ ├── images (文件夹存放封面图片) │ └── (其他的JSP页面) └── 项目配置文件3. 核心工具类与数据层打造稳健的“数据引擎”在动手写业务逻辑前我们先搭建好底层基础设施主要是数据库连接和通用的数据访问工具。这部分代码写好了后面写业务会非常顺畅。3.1 数据库连接池工具类直接在Servlet里获取Connection再关闭是极其低效且危险的做法。我们使用Druid连接池。首先在src下创建一个工具类DBUtil.javapackage com.music.util; import com.alibaba.druid.pool.DruidDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { // 使用静态内部类实现单例模式的DataSource private static class Holder { private static final DruidDataSource INSTANCE new DruidDataSource(); static { // 基础配置 INSTANCE.setUrl(jdbc:mysql://localhost:3306/music_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai); INSTANCE.setUsername(root); // 你的数据库用户名 INSTANCE.setPassword(your_password); // 你的数据库密码 INSTANCE.setDriverClassName(com.mysql.cj.jdbc.Driver); // 连接池配置根据实际需求调整 INSTANCE.setInitialSize(5); // 初始连接数 INSTANCE.setMinIdle(5); // 最小空闲连接 INSTANCE.setMaxActive(20); // 最大活跃连接 INSTANCE.setMaxWait(60000); // 获取连接超时时间毫秒 // 其他可选配置如检测连接有效性 INSTANCE.setValidationQuery(SELECT 1); } } public static DataSource getDataSource() { return Holder.INSTANCE; } public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } }为什么这么写这里使用了“静态内部类”的单例模式来创建DruidDataSource。这种方式能保证线程安全且实现了懒加载只有第一次调用getDataSource()时才会初始化。配置参数中serverTimezoneAsia/Shanghai是为了避免时区错误。validationQuery用于检测连接是否有效防止程序拿到已断开的连接。3.2 实体类与基础DAO设计根据数据库表创建对应的Java实体类Music.java放在com.music.entity包下。这就是JavaBean其属性与表字段一一对应并包含getter/setter方法。package com.music.entity; import java.util.Date; public class Music { private Integer id; private String title; private String singer; private String album; private Date releaseTime; // 注意这里用Date类型对应数据库的date private String musicTime; private String musicUrl; private String coverUrl; private Integer playCount; // 省略getter和setter方法... }接着我们创建一个基础的DAOData Access Object接口和实现用于封装所有对t_music表的操作。先定义接口MusicDao.javapackage com.music.dao; import com.music.entity.Music; import java.util.List; public interface MusicDao { // 查询所有音乐 ListMusic findAll(); // 根据ID查询音乐 Music findById(int id); // 根据歌名或歌手模糊查询 ListMusic findByKeyword(String keyword); // 增加播放次数 boolean increasePlayCount(int id); }然后是实现类MusicDaoImpl.java。这里会大量用到DBUtil.getConnection()和PreparedStatement来防止SQL注入。package com.music.dao.impl; import com.music.dao.MusicDao; import com.music.entity.Music; import com.music.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class MusicDaoImpl implements MusicDao { Override public ListMusic findAll() { ListMusic list new ArrayList(); String sql SELECT * FROM t_music ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql); ResultSet rs pstmt.executeQuery()) { while (rs.next()) { list.add(convertResultSetToMusic(rs)); } } catch (SQLException e) { e.printStackTrace(); } return list; } Override public ListMusic findByKeyword(String keyword) { ListMusic list new ArrayList(); // 使用LIKE进行模糊查询%放在参数两侧 String sql SELECT * FROM t_music WHERE title LIKE ? OR singer LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, % keyword %); pstmt.setString(2, % keyword %); try (ResultSet rs pstmt.executeQuery()) { while (rs.next()) { list.add(convertResultSetToMusic(rs)); } } } catch (SQLException e) { e.printStackTrace(); } return list; } // 将ResultSet的一行数据转换为Music对象 private Music convertResultSetToMusic(ResultSet rs) throws SQLException { Music music new Music(); music.setId(rs.getInt(id)); music.setTitle(rs.getString(title)); music.setSinger(rs.getString(singer)); music.setAlbum(rs.getString(album)); music.setReleaseTime(rs.getDate(release_time)); // 注意是getDate music.setMusicTime(rs.getString(music_time)); music.setMusicUrl(rs.getString(music_url)); music.setCoverUrl(rs.getString(cover_url)); music.setPlayCount(rs.getInt(play_count)); return music; } // 其他方法实现类似此处省略... }关键点解析使用Try-with-resources在try()括号中声明Connection,PreparedStatement,ResultSet无论是否发生异常它们都会在try块结束后自动关闭避免资源泄漏。这是Java 7后的最佳实践。使用PreparedStatement所有涉及变量的SQL都必须用它它能预编译SQL并安全地设置参数从根本上杜绝SQL注入。结果集到对象的转换单独抽出一个convertResultSetToMusic方法避免了在每个查询方法里重复写一堆set代码更清晰。4. 控制器与业务逻辑Servlet如何扮演“交通指挥”在MVC模式中Servlet充当控制器Controller。它接收来自浏览器的请求Request调用相应的业务层Service或数据层DAO处理然后将结果数据放入请求域或会话域最后转发Forward或重定向Redirect到JSP页面进行展示。4.1 首页音乐列表展示首先我们创建一个MusicListServlet用于处理首页请求展示所有音乐列表。package com.music.controller; import com.music.dao.MusicDao; import com.music.dao.impl.MusicDaoImpl; import com.music.entity.Music; 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(/music/list) public class MusicListServlet extends HttpServlet { private MusicDao musicDao new MusicDaoImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 调用DAO获取数据 ListMusic musicList musicDao.findAll(); // 2. 将数据存入请求域供JSP页面获取 req.setAttribute(musicList, musicList); // 3. 请求转发到展示页面 req.getRequestDispatcher(/music_list.jsp).forward(req, resp); } }代码解读WebServlet(/music/list)这是Servlet 3.0的注解配置它告诉容器当访问/music/list这个路径时由这个Servlet来处理。这替代了传统的在web.xml中配置servlet和servlet-mapping的方式更简洁。doGet方法处理HTTP GET请求。我们的首页访问就是一个GET请求。req.setAttribute(musicList, musicList)这是核心我们将查询到的musicList对象以键值对的形式放入了HttpServletRequest的请求域中。这个数据只在本次请求范围内有效。req.getRequestDispatcher(/music_list.jsp).forward(req, resp)请求转发。服务器内部将请求和响应对象传递给/music_list.jsp页面由该页面负责渲染输出。浏览器的地址栏不会改变。接下来创建对应的JSP页面music_list.jsp放在web目录下% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title音乐列表/title style /* 简单的CSS样式让页面好看点 */ table { border-collapse: collapse; width: 80%; margin: 20px auto; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } img { width: 60px; height: 60px; object-fit: cover; } /style /head body h2 styletext-align: center;音乐库/h2 table tr th封面/th th歌名/th th歌手/th th专辑/th th时长/th th播放/th /tr %-- 使用JSTL的c:forEach遍历musicList --% c:forEach varmusic items${musicList} tr tdimg src${pageContext.request.contextPath}${music.coverUrl} alt${music.title}封面 onerrorthis.srcdefault.jpg/td td${music.title}/td td${music.singer}/td td${music.album}/td td${music.musicTime}/td td %-- 播放按钮点击后跳转到播放Servlet并传递音乐ID --% a href${pageContext.request.contextPath}/music/play?id${music.id}播放/a /td /tr /c:forEach /table /body /htmlJSP页面关键点% taglib prefixc uri... %引入JSTL核心标签库。有了它我们才能使用c:forEach这样的标签。${musicList}这是EL表达式它会自动从页面上下文PageContext、请求域Request、会话域Session、应用域Application中寻找名为musicList的属性。因为我们之前在Servlet里用req.setAttribute存入了所以这里能直接取到。${pageContext.request.contextPath}这个表达式会动态获取当前Web应用的上下文路径。比如你的应用叫music它就会输出/music。这是解决路径问题的黄金法则。用它拼接资源路径如图片、CSS、JS、链接无论项目部署在什么路径下都能正确访问。onerrorthis.srcdefault.jpg一个实用的小技巧。如果图片路径错误加载失败会自动替换为一张默认图片避免页面出现破损图标。现在启动Tomcat访问http://localhost:8080/music/music/list你应该能看到一个包含测试音乐数据的表格页面。恭喜你第一个完整的“请求-处理-响应”链路跑通了4.2 搜索功能实现搜索功能是另一个典型的Servlet应用。我们创建一个MusicSearchServlet。WebServlet(/music/search) public class MusicSearchServlet extends HttpServlet { private MusicDao musicDao new MusicDaoImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 从请求中获取用户输入的搜索关键词 String keyword req.getParameter(keyword); // 2. 参数校验简单处理空值 if (keyword null || keyword.trim().isEmpty()) { // 如果关键词为空可以重定向回首页或者展示空结果 resp.sendRedirect(req.getContextPath() /music/list); return; } // 3. 调用DAO进行模糊查询 ListMusic resultList musicDao.findByKeyword(keyword.trim()); // 4. 将结果和关键词存入请求域 req.setAttribute(musicList, resultList); req.setAttribute(keyword, keyword.trim()); // 5. 转发到同一个展示页面复用music_list.jsp req.getRequestDispatcher(/music_list.jsp).forward(req, resp); } }在music_list.jsp页面顶部我们加上一个搜索框div styletext-align: center; margin: 20px; form action${pageContext.request.contextPath}/music/search methodget input typetext namekeyword placeholder输入歌名或歌手... value${param.keyword} input typesubmit value搜索 /form /div这里有个细节value${param.keyword}。param是EL表达式的内置对象代表请求参数。这样设置后在搜索结果显示页面搜索框里会保留用户刚才输入的关键词体验更好。4.3 音乐播放与播放次数统计播放功能稍微复杂一点它涉及两个动作一是跳转到播放页面或直接播放二是记录播放次数。我们设计两个Servlet协作。首先MusicPlayServlet负责处理播放请求并增加播放次数。WebServlet(/music/play) public class MusicPlayServlet extends HttpServlet { private MusicDao musicDao new MusicDaoImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String idStr req.getParameter(id); if (idStr null || !idStr.matches(\\d)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, 无效的音乐ID); return; } int musicId Integer.parseInt(idStr); // 1. 增加播放次数 boolean success musicDao.increasePlayCount(musicId); // 2. 根据ID查询音乐详细信息 Music music musicDao.findById(musicId); if (music null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, 音乐不存在); return; } // 3. 将音乐信息存入请求域并转发到播放页面 req.setAttribute(music, music); req.getRequestDispatcher(/play.jsp).forward(req, resp); } }在MusicDaoImpl中实现increasePlayCount方法Override public boolean increasePlayCount(int id) { String sql UPDATE t_music SET play_count play_count 1 WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, id); int rows pstmt.executeUpdate(); return rows 0; } catch (SQLException e) { e.printStackTrace(); return false; } }然后创建一个简单的播放页面play.jsp% page contentTypetext/html;charsetUTF-8 languagejava % html head title播放 - ${music.title}/title /head body h2正在播放: ${music.title} - ${music.singer}/h2 div img src${pageContext.request.contextPath}${music.coverUrl} alt封面 width200 /div div %-- 使用HTML5的audio标签播放音乐 --% audio controls autoplay source src${pageContext.request.contextPath}${music.musicUrl} typeaudio/mpeg 您的浏览器不支持 audio 元素。 /audio /div p专辑${music.album} | 发行时间${music.releaseTime} | 播放次数${music.playCount}/p a href${pageContext.request.contextPath}/music/list返回列表/a /body /html技术要点幂等性考虑/music/play这个接口如果被刷新会再次增加播放次数。在实际项目中这通常需要结合会话Session或更复杂的机制如用户ID音乐ID去重来防止刷量。这里为了简化我们暂时忽略。HTML5 Audio标签这是最简单的前端播放方案。controls属性提供播放控件autoplay让音乐自动播放。source标签指定音频源。你需要确保music_url字段的路径正确并且音频文件如MP3确实放在项目的web/audio/目录下。5. 项目优化与进阶思考从“能跑”到“好用”一个基础功能跑通的项目就像毛坯房。接下来我们进行一些必要的“装修”让它更健壮、更易用。5.1 统一字符编码过滤器中文乱码是JavaWeb开发中最常见的问题之一。GET和POST请求的乱码处理方式不同我们写一个CharacterEncodingFilter统一处理。package com.music.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; WebFilter(/*) // 过滤所有请求 public class CharacterEncodingFilter implements Filter { private String encoding UTF-8; Override public void init(FilterConfig filterConfig) throws ServletException { // 可以从web.xml读取配置的编码这里简化处理 String configEncoding filterConfig.getInitParameter(encoding); if (configEncoding ! null) { this.encoding configEncoding; } } Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 1. 设置请求和响应的编码 request.setCharacterEncoding(encoding); response.setCharacterEncoding(encoding); response.setContentType(text/html;charset encoding); // 2. 放行请求传递给下一个过滤器或Servlet chain.doFilter(request, response); } Override public void destroy() { // 清理资源 } }关键点WebFilter(/*)注解配置表示这个过滤器会拦截所有请求/*。chain.doFilter(request, response)这行代码至关重要它表示将处理后的请求和响应传递给过滤器链中的下一个组件可能是另一个过滤器也可能是最终的Servlet。如果忘记写这行请求就会在这个过滤器被截断无法到达后续处理环节。这个过滤器同时处理了请求参数request.setCharacterEncoding和响应输出response.setCharacterEncoding和setContentType的编码一劳永逸。5.2 使用JSTL与EL表达式彻底告别JSP脚本在早期的JSP中我们经常看到% ... %这种Java脚本片段。这种做法将业务逻辑和展示逻辑混在一起难以维护是典型的“Model 1”架构的弊端。现代JavaWeb开发强烈建议使用JSTLEL来替代。我们已经在前面的JSP中使用了c:forEach和${}。JSTL还有很多其他有用的标签比如条件判断c:if、c:choose格式化日期和数字的fmt:formatDate等。例如在music_list.jsp中我们可以优化日期显示和空结果提示%-- 引入JSTL格式化标签库 --% % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % %-- 在表格循环体内格式化日期 --% tdfmt:formatDate value${music.releaseTime} patternyyyy-MM-dd//td %-- 在表格前判断列表是否为空 --% c:if test${empty musicList} p styletext-align: center; color: gray;没有找到相关的音乐。/p /c:if5.3 静态资源处理与路径问题终极解决方案我们的音乐文件MP3和图片文件JPG都是静态资源。在JavaWeb项目中静态资源通常直接放在web目录或其子目录下如web/audio/,web/images/。Tomcat容器内置了一个叫做DefaultServlet的Servlet它会自动处理对这些静态资源的请求。但是路径问题经常困扰新手。记住一个原则所有在JSP/HTML中引用的资源路径src, href, action最好都使用${pageContext.request.contextPath}作为前缀。例如错误的写法img src/images/xxx.jpg。如果应用部署在/music下它会去请求http://localhost:8080/images/xxx.jpg这显然是错的。正确的写法img src${pageContext.request.contextPath}/images/xxx.jpg。它会动态生成/music/images/xxx.jpg永远正确。对于CSS和JS文件也建议放在web/css/和web/js/下并用同样的方式引用。5.4 项目部署与数据库连接池配置优化开发完成后我们需要将项目打包成WAR文件部署到独立的Tomcat服务器上。在IDEA中你可以通过Build - Build Artifacts...来生成WAR包。部署时一个常见的坑是数据库连接池配置。我们在DBUtil.java中硬编码了数据库连接信息URL、用户名、密码。这在生产环境是不安全的也不利于不同环境开发、测试、生产切换。标准的做法是将这些配置外置到WEB-INF下的一个属性文件如druid.properties中或者使用JNDIJava Naming and Directory Interface让Tomcat来管理数据源。这里给出使用属性文件的改进方案在src目录或WEB-INF/classes下创建druid.propertiesdruid.urljdbc:mysql://localhost:3306/music_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai druid.usernameroot druid.passwordyour_password druid.initialSize5 druid.minIdle5 druid.maxActive20修改DBUtil.java的静态初始化块从属性文件读取配置static { try (InputStream is DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(is); INSTANCE.setUrl(props.getProperty(druid.url)); INSTANCE.setUsername(props.getProperty(druid.username)); // ... 设置其他属性 } catch (IOException e) { throw new RuntimeException(加载Druid配置失败, e); } }这样在部署到不同环境时只需替换druid.properties文件即可无需重新编译代码。6. 常见问题排查与调试技巧即使按照步骤操作你也可能会遇到一些问题。这里列出几个高频问题及其排查思路。问题1访问Servlet报404错误。检查1Servlet路径映射是否正确。确认WebServlet注解里的路径或者web.xml里的url-pattern。访问的URL是否与之匹配注意大小写。检查2项目是否成功部署到Tomcat。查看IDEA的Tomcat控制台启动日志确认你的项目Context Path是什么。访问http://localhost:8080/你的ContextPath/Servlet路径。检查3编译后的类文件是否存在。检查Tomcat的工作目录通常是[Tomcat安装目录]/webapps/[你的项目]/WEB-INF/classes下你的Servlet类文件.class是否存在。如果不存在可能是IDEA没有自动编译部署尝试Build - Rebuild Project。问题2页面显示中文乱码。确保过滤器生效检查CharacterEncodingFilter是否被正确配置和加载。可以在doFilter方法开始加一句System.out.println(过滤器执行);来调试。检查数据库连接字符串JDBC URL中必须包含?useUnicodetruecharacterEncodingUTF-8。检查数据库、表、字段的字符集确保它们都是utf8mb4。检查JSP文件本身的编码在IDEA中右键点击JSP文件选择File Encoding确保是UTF-8并且“Transparent native-to-ascii conversion”最好勾选上。问题3静态资源图片、音乐无法加载。检查文件路径使用浏览器开发者工具F12查看“网络(Network)”标签页看看加载资源时返回的是404还是其他错误。核对请求的URL和文件实际存放的路径。检查Tomcat配置有时Tomcat的server.xml中配置了额外的静态资源路径可能会产生冲突。但我们的项目结构是标准的一般不会。权限问题Linux/Mac确保Tomcat进程有权限读取你项目web目录下的文件。问题4增加播放次数后页面显示的数字没变。这是一个典型的“请求转发 vs 重定向”理解问题。在我们的MusicPlayServlet中增加播放次数后我们使用了req.getRequestDispatcher(/play.jsp).forward(req, resp);即请求转发。这意味着播放页面 (play.jsp) 和Servlet在同一次请求中。play.jsp页面里${music.playCount}显示的是我们从数据库重新查询出来的Music对象里的playCount这个值已经是增加后的了。所以逻辑是正确的。如果你发现数字没变可能是以下原因数据库更新失败检查increasePlayCount方法中的SQL执行是否成功查看控制台是否有异常。浏览器缓存刷新页面试试。事务问题本例不涉及如果后续引入Service层和事务需要确保更新和查询在同一个事务内才能读到最新的数据。这个小型音乐网站项目虽然功能简单但它完整地串联了JavaWeb开发的核心流程和技术点。从环境搭建、数据库设计、数据访问层(DAO)、控制器(Servlet)、到视图层(JSP)再到过滤器、路径处理等细节每一步都是企业级开发的缩影。通过亲手实现它你不仅能巩固技术更能建立起对Web应用架构的直观理解。接下来你可以尝试在此基础上扩展更多功能比如用户登录注册、音乐收藏、分页查询、使用Ajax实现无刷新播放等让这个“骨架”逐渐丰满起来。本文还有配套的精品资源点击获取