JavaWeb开发实战源码:从跑通到拆解改造的完整指南

发布时间:2026/10/5 7:17:53
JavaWeb开发实战源码:从跑通到拆解改造的完整指南 简介这份JavaWeb开发实战源码面向希望系统掌握Java Web开发流程的个人学习者尤其适合正在从基础语法向完整项目实践过渡的初学者与进阶开发者。资源以webdemo项目为主线通过分层结构演示MVC设计模式Model层处理业务逻辑与数据库交互View层由JSP页面、CSS样式和JavaScript脚本构成Controller层负责请求接收与视图调度帮助读者直观理解从页面制作到后端处理再到数据存储的完整链路。压缩包共450个文件约6.75MB包含288个png、32个gif等界面素材22个jsp页面、18个java源文件、36个class编译文件、16个js脚本、11个xml配置及4个jar依赖覆盖Servlet、过滤器、验证码等典型模块。目前已有54人学习。源码中还涉及JDBC连接数据库、SQL增删改查、预编译语句防注入及输入过滤防XSS等实用技巧可作为构建个人项目、优化代码结构与提升可维护性的参考范例。1. JavaWeb开发实战源码从能跑起来到能改得动中间隔着什么很多人拿到一份 JavaWeb 开发实战源码第一反应是找 IDEA 打开、配 Tomcat、点运行。跑起来了页面出来了然后呢想改个登录逻辑发现 Servlet 里套着 JSPJSP 里嵌着 JavaScriptJavaScript 又调了一个不知道返回什么的接口。改一处崩三处。这不是源码的问题是没搞清楚这份源码的架构分层和数据流向。JavaWeb 开发实战源码的价值不在于“能跑”而在于“能拆”。一份典型的实战源码通常包含 Servlet/JSP 或 Spring Boot 两种技术栈配合 MySQL 做数据持久化用 Maven 管理依赖。它解决的核心问题是让你在一个完整的业务闭环里看到请求从浏览器发出到数据库返回的完整链路。适合谁适合已经学过 Java 基础、能写简单类和方法但一遇到“项目”就不知道从哪下手的人。也适合工作一两年、一直在写增删改查、想回头把基础链路理清楚的开发者。接下来的内容按“先跑通、再拆解、后改造”的顺序推进每一步都落到具体操作上。2. 把源码跑起来IDEA 配置 JavaWeb 项目的完整链路2.1 环境准备与项目导入的四个关键检查点拿到一份 JavaWeb 源码别急着点 Open。先看根目录下有没有pom.xml。有就是 Maven 项目没有看有没有WEB-INF/web.xml和lib目录那是传统动态 Web 项目。两种项目的导入方式不同配错了后面全是坑。我一般按这个顺序检查第一步确认 JDK 版本。打开pom.xml看maven.compiler.source或java.version属性。如果是 1.8IDEA 的 Project Structure 里 SDK 就选 1.8如果是 17就选 17。版本不匹配编译期就报“无效的目标发行版”。第二步确认 Tomcat 版本。传统 Servlet 项目用 Tomcat 8 或 9 都行但 Spring Boot 项目如果打的是 jar 包内置 Tomcat不需要额外配。看pom.xml里有没有spring-boot-starter-web有就是内置的。第三步检查数据库连接配置。通常在src/main/resources下找application.properties、application.yml或jdbc.properties。重点看url、username、password三项。数据库名、端口、时区参数一个都不能错。第四步看有没有 SQL 初始化脚本。很多实战源码会在src/main/resources或项目根目录放一个.sql文件。先建库再导数据最后启动项目。顺序反了启动时报“Table doesn‘t exist”。提示如果项目用的是 Maven 多模块结构先看父pom.xml的modules标签确认要启动的是哪个子模块。常见的是web模块或admin模块别在common模块里找启动类。2.2 用 Maven 命令拉依赖并启动项目的实操步骤环境检查完开始动手。以下命令在项目根目录执行Windows 用mvnw.cmdMac/Linux 用./mvnw如果项目没有 wrapper就用全局mvn。# 第一步清理本地构建缓存避免旧编译产物干扰 mvn clean # 第二步跳过测试编译并拉取所有依赖 # -DskipTests 跳过测试执行但不跳过测试代码编译 # -U 强制更新 SNAPSHOT 依赖避免本地缓存过期 mvn install -DskipTests -U # 第三步如果是 Spring Boot 项目用插件直接启动 mvn spring-boot:run # 第四步如果是传统 Servlet 项目用 Tomcat 插件启动 # 注意pom.xml 里必须配置了 tomcat7-maven-plugin 或 cargo-maven2-plugin mvn tomcat7:runmvn clean的作用是删除target目录避免上一次编译的 class 文件残留导致“改了代码没生效”的玄学问题。mvn install会把当前项目打包并安装到本地仓库多模块项目必须执行这一步否则子模块之间互相依赖时找不到 jar。-U参数在团队协作时特别有用别人更新了 SNAPSHOT 版本你不加-U拉下来的还是旧包。启动成功后控制台会输出端口号。Spring Boot 默认 8080传统 Tomcat 插件默认也是 8080。如果端口被占用改application.properties里的server.port或者 Tomcat 插件的port配置。浏览器访问http://localhost:8080看到登录页或首页说明链路通了。如果 404检查web.xml里的url-pattern或 Spring Boot 的RequestMapping路径。如果 500看控制台异常栈通常是数据库连接失败或 SQL 语法错误。2.3 数据库初始化建库、导数据、改连接串数据库是 JavaWeb 项目的“黑匣子”跑不起来十有八九卡在这。我一般按这个流程走先看 SQL 文件里的建库语句。常见的是CREATE DATABASE xxx DEFAULT CHARACTER SET utf8mb4。注意字符集utf8和utf8mb4在存储 emoji 时行为不同实战项目建议统一用utf8mb4。然后导入数据。命令行方式# 登录 MySQL mysql -u root -p # 创建数据库如果 SQL 文件里没有建库语句 CREATE DATABASE javaweb_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 切换到该数据库 USE javaweb_demo; # 导入 SQL 文件注意路径用绝对路径或当前目录相对路径 source /path/to/init.sql;导入完成后回到项目的配置文件把url改成jdbc:mysql://localhost:3306/javaweb_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。serverTimezone不配MySQL 8 会报“The server time zone value is unrecognized”。useSSL在本地开发设为false省去证书配置的麻烦。注意如果 SQL 文件里用了DROP TABLE IF EXISTS导入前确认没有重要数据。生产环境永远不要直接跑这种脚本。3. 拆解源码结构Servlet、JSP 与 Spring Boot 的分层逻辑3.1 传统 Servlet JSP 项目的请求流转路径传统 JavaWeb 项目的核心是web.xml和 Servlet 映射。一个登录请求的完整路径是这样的浏览器发POST /loginTomcat 根据web.xml里的url-pattern找到对应的 Servlet 类Servlet 调用 Service 层Service 调用 DAO 层查数据库结果封装成对象Servlet 把对象存到request域转发到login.jspJSP 渲染 HTML 返回浏览器。这个链路里Servlet 是控制器JSP 是视图Service 和 DAO 是模型。但很多实战源码为了“简单”把业务逻辑直接写在 Servlet 的doPost方法里SQL 也拼在 Servlet 里。这种代码能跑但改不动。你加一个字段要同时改 Servlet、JSP、SQL 三处。看源码时重点找这几个文件web.xml看servlet和servlet-mapping理清 URL 和类的对应关系。src/main/java/.../servlet/目录看每个 Servlet 的doGet和doPost方法。src/main/webapp/WEB-INF/目录看 JSP 文件的位置和命名。src/main/java/.../dao/目录看数据库操作是 JDBC 还是 MyBatis。如果 DAO 层用的是原生 JDBC注意看Connection、PreparedStatement、ResultSet有没有在finally块里关闭。没关跑一段时间就报“Too many connections”。3.2 Spring Boot 项目的注解与自动配置拆解Spring Boot 项目没有web.xml靠注解和自动配置。启动类上的SpringBootApplication拆开看是三个注解SpringBootConfiguration、EnableAutoConfiguration、ComponentScan。ComponentScan默认扫描启动类所在包及其子包所以启动类要放在最外层包下。控制器用RestController或Controller。RestController等于Controller加ResponseBody返回 JSONController返回视图名配合模板引擎Thymeleaf、JSP渲染页面。Service 层用ServiceDAO 层用Repository或 MyBatis 的Mapper。依赖注入用Autowired或构造器注入。构造器注入是官方推荐因为能保证依赖不为 null也方便写单元测试。配置文件application.yml的层级结构比.properties更直观。比如server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/javaweb_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entitymapper-locations告诉 MyBatis 去哪找 XML 映射文件type-aliases-package让 XML 里可以直接写类名不用写全限定名。这两个配错启动时报“Invalid bound statement”或“ClassNotFoundException”。3.3 从源码里找到业务入口的通用方法不管什么架构找业务入口的通用方法是从浏览器地址栏的 URL 反推。比如访问http://localhost:8080/user/list就在代码里搜user/list或RequestMapping(“/user”)。找到控制器方法后看它调了哪个 Service 方法再跟到 DAO 层最后看 SQL。另一个方法是看日志。启动时控制台会打印映射关系Spring Boot 打印Mapped “{[/user/list],methods[GET]}”传统 Servlet 打印Mapping: /user/list。把日志级别调到DEBUG能看到更详细的 SQL 和参数。如果源码里用了拦截器或过滤器注意看preHandle方法的返回值。返回false请求就被截断了后面的控制器根本不会执行。登录校验、权限控制通常在这里实现。4. 改造实战在现有源码上增加一个完整业务模块4.1 从数据库表到实体类的字段映射假设要在现有源码上加一个“公告管理”模块。第一步是建表CREATE TABLE notice ( id int NOT NULL AUTO_INCREMENT COMMENT ‘主键’, title varchar(200) NOT NULL COMMENT ‘公告标题’, content text COMMENT ‘公告内容’, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘更新时间’, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT‘公告表’;字段类型要和 Java 类型对应int对Integervarchar对Stringdatetime对LocalDateTime或Date。text对String。AUTO_INCREMENT对应GeneratedValue(strategy GenerationType.IDENTITY)。实体类放在entity包下类名和表名对应字段名和列名对应。如果列名是下划线风格create_timeJava 字段用驼峰createTimeMyBatis 开启map-underscore-to-camel-case: true自动映射。4.2 用 MyBatis 写 DAO 层与 XML 映射文件DAO 接口Mapper public interface NoticeMapper { // 查询所有公告按创建时间倒序 ListNotice selectAll(); // 根据 ID 查询单条公告 Notice selectById(Integer id); // 插入公告useGeneratedKeys 让主键回填到实体对象 Insert(“INSERT INTO notice(title, content) VALUES(#{title}, #{content})”) Options(useGeneratedKeys true, keyProperty “id”) int insert(Notice notice); // 更新公告 int update(Notice notice); // 删除公告 int deleteById(Integer id); }XML 映射文件NoticeMapper.xml?xml version“1.0” encoding“UTF-8”? !DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” “http://mybatis.org/dtd/mybatis-3-mapper.dtd” mapper namespace“com.example.mapper.NoticeMapper” select id“selectAll” resultType“Notice” SELECT id, title, content, create_time, update_time FROM notice ORDER BY create_time DESC /select select id“selectById” resultType“Notice” SELECT id, title, content, create_time, update_time FROM notice WHERE id #{id} /select update id“update” UPDATE notice SET title #{title}, content #{content} WHERE id #{id} /update delete id“deleteById” DELETE FROM notice WHERE id #{id} /delete /mappernamespace必须和 DAO 接口的全限定名一致否则 MyBatis 找不到映射。resultType写别名Notice需要配置type-aliases-package写全限定名也可以。#{id}是预编译参数能防止 SQL 注入${id}是字符串拼接不要用。4.3 控制器与页面联调从请求参数到视图渲染控制器Controller RequestMapping(“/notice”) public class NoticeController { Autowired private NoticeService noticeService; // 公告列表页 GetMapping(“/list”) public String list(Model model) { ListNotice notices noticeService.selectAll(); model.addAttribute(“notices”, notices); return “notice/list”; // 对应 /WEB-INF/views/notice/list.jsp } // 跳转到新增页面 GetMapping(“/add”) public String addPage() { return “notice/add”; } // 处理新增提交 PostMapping(“/add”) public String add(Notice notice) { noticeService.insert(notice); return “redirect:/notice/list”; } // 删除公告 GetMapping(“/delete/{id}”) public String delete(PathVariable Integer id) { noticeService.deleteById(id); return “redirect:/notice/list”; } }GetMapping处理 GET 请求PostMapping处理 POST 请求。redirect:前缀表示重定向浏览器地址栏会变不加前缀默认是转发地址栏不变。PathVariable从 URL 路径里取参数RequestParam从查询字符串里取参数。JSP 页面list.jsp% page contentType“text/html;charsetUTF-8” language“java” % % taglib prefix“c” uri“http://java.sun.com/jsp/jstl/core” % html headtitle公告列表/title/head body table border“1” trthID/thth标题/thth创建时间/thth操作/th/tr c:forEach items“${notices}” var“notice” tr td${notice.id}/td td${notice.title}/td td${notice.createTime}/td td a href“${pageContext.request.contextPath}/notice/delete/${notice.id}”删除/a /td /tr /c:forEach /table a href“${pageContext.request.contextPath}/notice/add”新增公告/a /body /html${pageContext.request.contextPath}获取项目上下文路径避免部署路径变化导致链接失效。c:forEach遍历控制器传过来的notices集合。JSTL 标签库需要在pom.xml里引入jstl依赖否则报“The absolute uri cannot be resolved”。联调时先访问/notice/list看页面有没有数据。没有数据检查数据库里有没有记录SQL 有没有执行。有数据但页面报错看 JSP 里的 EL 表达式有没有写错字段名和实体类是否一致。新增后重定向到列表页看新记录有没有出现。没出现看事务有没有提交MyBatis 的insert返回值是不是大于 0。5. 避坑与排查JavaWeb 源码改造中最容易翻车的五个点5.1 中文乱码从 Tomcat 编码到数据库字符集现象页面输入中文保存到数据库变成???或者页面显示乱码。原因有三层Tomcat 的 URI 编码、请求体编码、数据库字符集。Tomcat 8 以后默认 URI 编码是 UTF-8但请求体POST 表单默认还是 ISO-8859-1。数据库如果建库时用了latin1存中文直接丢。解决在web.xml里加CharacterEncodingFilter或者 Spring Boot 在application.properties里加server.servlet.encoding.charsetUTF-8和server.servlet.encoding.forcetrue。数据库连接串加useUnicodetruecharacterEncodingutf8。建库建表统一用utf8mb4。JSP 页面顶部加% page contentType“text/html;charsetUTF-8” %。5.2 数据库连接池耗尽连接没关的典型表现现象项目跑一段时间后报“Cannot get a connection, pool exhausted”或“Too many connections”。原因DAO 层用了原生 JDBCConnection、PreparedStatement、ResultSet没有在finally块里关闭。或者用了连接池但代码里手动getConnection()后忘了close()。解决用 try-with-resources 语法try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 处理结果集 } catch (SQLException e) { e.printStackTrace(); }连接池配置里加maxActive、maxIdle、minIdle和maxWait。maxWait设太小高并发时直接抛异常设太大请求堆积。一般设 3000 毫秒左右。5.3 静态资源 404Spring Boot 与 Tomcat 的路径差异现象CSS、JS、图片加载不出来浏览器控制台报 404。原因Spring Boot 默认静态资源放在src/main/resources/static或public目录下传统 Tomcat 项目放在webapp根目录或WEB-INF外面。放错位置Spring Boot 的默认配置找不到。解决Spring Boot 项目把静态资源放static目录访问路径是http://localhost:8080/css/style.css不要加static前缀。传统项目放webapp/static下JSP 里用${pageContext.request.contextPath}/static/css/style.css。如果用了拦截器检查有没有排除静态资源路径比如excludePathPatterns(“/static/**”)。5.4 事务不生效Service 层注解失效的几种情况现象插入主表和子表主表成功子表失败数据不一致。原因Transactional注解没生效。常见情况有方法不是public的同类内部方法直接调用没走代理异常被 catch 了没抛出去数据库引擎是 MyISAM不支持事务。解决Transactional加在public方法上。如果是同类内部调用把方法拆到另一个 Service 里或者用AopContext.currentProxy()。异常要抛RuntimeException默认只回滚运行时异常受检异常要加rollbackFor Exception.class。数据库表引擎改成 InnoDB。5.5 Maven 依赖冲突NoSuchMethodError 的排查思路现象启动时报NoSuchMethodError或ClassNotFoundException但代码里明明有这个类。原因同一个库引入了多个版本Maven 的“最近胜利”原则选了旧版本。比如项目直接依赖 A 的 2.0但 B 依赖 A 的 1.0Maven 可能选 1.0。解决用mvn dependency:tree打印依赖树找到冲突的库。用exclusions排除旧版本或者用dependencyManagement统一版本号。IDEA 的 Maven 面板里也有“Show Dependencies”按钮图形化看冲突更直观。6. 进阶技巧用源码调试和单元测试验证改造结果改造完一个模块怎么确认没改坏其他功能我的习惯是先写单元测试覆盖 Service 层再用 Postman 或浏览器走一遍完整流程最后看日志有没有异常。单元测试用 JUnit 5 加 Mockito。Service 层测试不依赖 Spring 容器直接 mock DAOExtendWith(MockitoExtension.class) class NoticeServiceTest { Mock private NoticeMapper noticeMapper; InjectMocks private NoticeServiceImpl noticeService; Test void testSelectAll() { // 准备数据 Notice notice new Notice(); notice.setId(1); notice.setTitle(“测试公告”); when(noticeMapper.selectAll()).thenReturn(List.of(notice)); // 执行 ListNotice result noticeService.selectAll(); // 断言 assertEquals(1, result.size()); assertEquals(“测试公告”, result.get(0).getTitle()); verify(noticeMapper, times(1)).selectAll(); } }Mock创建虚拟的 DAO 对象InjectMocks把虚拟对象注入 Service。when(...).thenReturn(...)定义 mock 方法的返回值。verify检查方法被调了几次。这样测试不连数据库跑得快适合频繁执行。集成测试用SpringBootTest加AutoConfigureMockMvc模拟 HTTP 请求SpringBootTest AutoConfigureMockMvc class NoticeControllerTest { Autowired private MockMvc mockMvc; Test void testList() throws Exception { mockMvc.perform(get(“/notice/list”)) .andExpect(status().isOk()) .andExpect(view().name(“notice/list”)) .andExpect(model().attributeExists(“notices”)); } }MockMvc不启动真实服务器但走完整的 Spring MVC 流程能测到拦截器、参数绑定、视图解析。比 Postman 手动测更可靠能集成到 CI 里。调试源码时IDEA 的断点条件很有用。比如在NoticeController的list方法上打断点右键设置条件notices.size() 0只有列表非空时才停下来。看 MyBatis 执行的 SQL在application.properties里加logging.level.com.example.mapperDEBUG控制台会打印 SQL 语句和参数。我自己的习惯是每改一个模块先跑单元测试再跑集成测试最后手动点一遍页面。三步都过了才提交代码。这个习惯帮我省了很多“改 A 崩 B”的后悔药。源码是别人的但改造后的代码是自己的测试就是自己的安全网。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询