JavaWeb项目启动实战:旅游网从环境配置到成功运行全解析 很多刚开始接触 JavaWeb 实战的同学拿到“旅游网”这类项目第一时间就懵了代码还没写一行光是把项目跑起来就折腾了一整天。一会儿端口被占用一会儿数据库连不上一会儿页面 404好不容易启动成功又发现乱码满天飞。这篇内容就是把“启动”这个最容易劝退的环节单独拆开带你把一个 JavaWeb 项目从零到能访问每一步的来龙去脉都说清楚。这篇东西适合谁看已经学完 Servlet、JSP、JDBC 基础但没完整做过项目的同学也适合项目写到一半被各种环境问题卡住、想回头梳理的开发者。我会沿用经典的 Servlet JSP MySQL Tomcat 技术栈先跑通再谈优化。下面说的每一步都是我实际搭过、踩过坑之后整理出来的复盘经验。1. 为什么启动阶段最容易被“环境”劝退1.1 这个旅游网项目到底在做什么旅游网项目的本质是一个典型的信息管理系统加电商逻辑的混合体。从用户端看有注册登录、旅游线路列表展示、按分类和关键词搜索、线路详情查看、收藏线路等功能从后台管理看有分类管理、线路上下架、用户管理等模块。技术落点上它没有引入特别花哨的框架——表现层用 JSP 渲染页面Servlet 做控制层分发请求业务逻辑放 Service 层数据访问用 JDBC 连接池 MySQL。这个组合常被叫做“JavaWeb 三件套土味开发”但恰恰是因为它土反而把 HTTP 请求处理、会话跟踪、请求参数封装、事务边界这些底层逻辑暴露得清清楚楚。等你后面跳到 Spring Boot MyBatis会发现那些注解和自动配置背后其实就是这一套东西的封装。所以这个项目启动的意义不在于“做出一个多豪华的网站”而在于把以前分散学的知识点串成一条完整链路浏览器发起请求 → Tomcat 接收 → Servlet 路由 → Service 处理 → DAO 访问数据库 → 返回数据 → JSP 渲染页面 → 浏览器展示。启动阶段就是把这条链路的骨架立起来哪怕页面很简陋哪怕只有一个首页在转都意味着全链路是通的。1.2 技术选型背后的取舍逻辑有同学可能问现在都快全民 Spring Boot 了为什么还要用 Servlet JSP 这种老搭配我的看法是学习期和实践期是两个阶段。实践期用 Spring Boot 当然效率高但在学习期你最好先见过 Servlet 怎么注册、Filter 怎么拦截、Session 怎么跨页面共享数据然后再去用框架否则你会陷入“注解一敲功能就有出了问题却完全不知道底层发生了什么”的困境。被教育项目广泛选择的另一个原因是环境要求极低一台电脑上装好 JDK、Tomcat、MySQL、Maven不需要额外申请云资源不需要容器环境任何一台普通开发机能跑。而它在部署时也足够可控——打包成 WAR 包扔进 Tomcat 的 webapps 目录就能运行这种“所见即所得”的部署方式特别适合用来建立部署信心。不过我必须诚实地提醒如果你已经有过一两个完整项目经验想靠这个项目积累生产级技能那它确实偏教学向。真正的生产项目不会用 JSP 作为视图方案也不会手写那么多 JDBC 样板代码。但作为启动第一课它的性价比非常高。2. 开发环境版本搭配与踩坑记录2.1 版本组合建议JavaWeb 项目启动最常见的混乱来源就是版本组合不匹配。我从实用角度给你一个经过验证的组合直接用基本不会出大问题组件推荐版本说明JDK1.8 或 11对应 Tomcat 8.5/9.0不要直接用 JDK 17 跑老项目Tomcat8.5.x 或 9.0.x8.5 兼容性最好9.0 支持 Servlet 4.0Maven3.6.x / 3.8.x3.9 以上版本也能用但仓库配置要注意MySQL5.7 或 8.08.0 驱动类名和连接串有变化后面细说IDEA2022 版以上即可社区版也能完成所有功能不必强行追求旗舰版为什么我会推荐 JDK 1.8 而不是 JDK 17因为这个项目的技术栈跑在老版本上久经考验。JDK 17 虽然在语法上更好但 Tomcat 8.5 对 JDK 17 的支持并不完美而如果升级到 Tomcat 10 又会遇到 Jakarta EE 命名空间变化——包名从 javax.* 变成 jakarta.*你写代码时被网上各种版本混乱的教程搞晕的概率会大幅上升。我见过不少同学在启动阶段一卡就是两三天最后发现是 JDK 版本和 Tomcat 版本打架。强烈建议第一次跑这个项目老老实实用 JDK 1.8 Tomcat 8.5。后面熟了要升级再升级不要一上来就挑战高版本组合。2.2 环境变量与开发工具配置细节装好 JDK 之后记得检查两个环境变量JAVA_HOME 指向 JDK 安装目录PATH 里加上 %JAVA_HOME%\bin。配置完成后在命令行执行java -version能正确输出版本号即可。Tomcat 这边有个小坑千万不要把 Tomcat 解压到带空格的路径下比如C:\Program Files\Apache Tomcat这种启动脚本在某些版本下会解析出错。我习惯放在D:\dev\apache-tomcat-8.5.xx这类纯英文无空格的目录这个习惯帮我省了不少时间。用 IDEA 开发时有几个设置值得留意。文件编码统一设置为 UTF-8Settings → Editor → File Encodings把 Global Encoding、Project Encoding、Properties Files 全部改成 UTF-8。然后还需要设置 JVM 启动参数-Dfile.encodingUTF-8否则控制台打印中文日志会一片乱码。还有一点容易被忽略IDEA 里的 Tomcat 不是直接用你解压的那个目录而是会以它为模板创建一个受管实例部署区在C:\Users\你的用户名\.IntelliJIdea202x\system\tomcat\...。这导致一个非常常见的现象——你明明改了解压目录下 conf/server.xml 里的端口但启动时端口根本没变因为真正生效的是 IDEA 生成的配置文件副本。要找它在控制台日志前几行里找Using CATALINA_BASE的输出路径去那边改配置别改错了地方。3. 数据库设计旅游网的表结构与初始化脚本3.1 核心业务表怎么划分旅游网项目的表并不复杂但设计思路上有几个点值得展开。我按业务模块把表分成四类用户类、线路类、分类类、收藏关系类。用户表tab_user存用户 id、用户名、密码、姓名、手机号、邮箱、状态等字段。密码不要用明文教材项目无所谓但我建议至少用 MD5 做一次性摘要登录校验时比对摘要值而不是明文。线路分类表tab_category它的作用是给旅游线路提供一个粗粒度的导航维度比如“国内游、境外游、周边游”等。字段就两个核心cid 和 cname外加一个排序号。线路表tab_route这是整个系统信息量最密集的表包含线路 id、线路名称、所属分类、价格、出发时间、线路介绍、是否上架等。注意这里我设置了一个外键指向分类表用于关联查询。收藏表tab_favorite这是用户与线路之间的多对多关系表。一个用户可以收藏多条线路一条线路可以被多个用户收藏。所以这张表的联合主键是 uid rid还要加一个收藏时间字段。可能有人觉得项目这么小还要不要设计外键我的看法是学习项目该用外键就用因为你需要练习级联删除、练习关联查询。到生产项目再用工具层面的逻辑外键替代物理外键是另外一个话题。在这里物理外键反而能帮助你直观理解表关系。3.2 建表脚本的实操要素字符集统一用 utf8mb4不要只写 utf8。区别在于 MySQL 的 utf8 只支持三个字节的字符像一些生僻字和特殊符号存不进去utf8mb4 才是完整支持 Unicode 四字节编码。建库时建议显式指定排序规则CREATE DATABASE tourism_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后建表。以核心线路表为例CREATE TABLE tab_route ( rid INT PRIMARY KEY AUTO_INCREMENT, rname VARCHAR(100) NOT NULL, price DOUBLE NOT NULL, route_introduce VARCHAR(1000), cid INT, rdate DATE, is_theme INT, CONSTRAINT fk_route_category FOREIGN KEY (cid) REFERENCES tab_category(cid) );写初始化脚本时最好把 INSERT 语句也一并写好方便后面开发时测试数据随手可用。我不建议手工去 Navicat 里慢慢敲直接把整个 SQL 文件执行一次表结构加数据全齐了。另一个实操细节脚本文件里不要写USE语句切换库而是在工具连接时选中 tourism_db 再执行。这样可以防止你在别的库上误操作。如果你用的是登录 MySQL 命令行执行那可以写但无论如何执行前先看一眼当前连接的是哪个库。4. Maven 工程骨架从空目录到可运行的 Web 项目4.1 包结构与坐标设计创建 Maven 项目时用的是 maven-archetype-webapp 骨架还是直接建一个空 Maven 项目然后手动补 webapp 目录结构我建议后者因为 Web 骨架默认生成的目录往往缺胳膊少腿还得手动调整不如一次性建对。手动建立的标准结构如下tourism-web/ ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com/example/tourism │ │ │ ├── domain // 实体类 │ │ │ ├── dao // 数据访问层 │ │ │ ├── service // 业务层 │ │ │ ├── web // Servlet 控制层 │ │ │ └── util // 工具类 │ │ ├── resources │ │ │ └── jdbc.properties // 数据库配置 │ │ └── webapp │ │ ├── index.html │ │ ├── WEB-INF │ │ │ ├── web.xml │ │ │ └── jsp │ │ ├── css │ │ ├── js │ │ └── images这里的关键点是JSP 文件一定要放到WEB-INF下面吗不一定。教材项目为了简单经常把 JSP 跟静态资源放在同一级比如webapp/jsp/login.jsp这样浏览器可以直接访问。但更规范的做法是放进WEB-INF/jsp由 Servlet 转发访问这样可以防止未登录用户直接摸到 JSP 文件路径。为了启动阶段不死磕我建议先放webapp/jsp/直接访问把系统跑通后续再考虑限制访问的问题。4.2 pom.xml 依赖清单这个项目用到的主要依赖就几类Servlet API、JSP API、MySQL 驱动、连接池、日志组件、单元测试。不要用 Spring 相关的东西保持纯粹的 Servlet 技术栈。properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.2/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.8/version /dependency /dependencies两个容易踩的坑提前说明。第一Servlet API 的作用域是provided意思是编译和测试时用它打包部署时交给 Tomcat 提供避免整套 API 打进了 WAR 包导致和 Tomcat 自带类冲突。第二MySQL 驱动版本要和数据库版本匹配。如果连的是 MySQL 8.x驱动 artifact 建议换成com.mysql:mysql-connector-j:8.0.x驱动类是com.mysql.cj.jdbc.Driver连接串也要带serverTimezoneAsia/Shanghai否则报时区错误。这个坑在后面第 5 部分会展开讲。4.3 配置 Tomcat 部署并访问在 IDEA 里配置 Tomcat 的步骤其实很简单Run → Edit Configurations → 左上角加号 → Tomcat Server → Local。然后选择 Application Server 指向你解压的 Tomcat 目录在 Deployment 页签点加号选 Artifact选择tourism-web:war explodedApplication context 填/tourism_web。这里解释一下为什么要用war exploded它是“爆炸式部署”相当于把项目直接映射到 Tomcat 的虚拟目录下修改代码后只需要重新构建不需要重打包 WAR开发效率高。而war包模式每次修改都要重新打包再发适合发布环境不适合开发调试。启动后访问地址就是http://localhost:8080/tourism_web/。注意最后这个带斜杠的路径很多人会漏掉如果直接访问/tourism_web部分 Tomcat 版本会给你返回一个 302 重定向多一次跳转不说有时还会因为相对路径问题导致 CSS 加载失败。反正记住访问时统一带尾斜杠。5. 启动即崩溃最少遇到的三类问题5.1 端口被占用与多重启动端口占用是启动项目时出现的第一个拦路虎。Tomcat 默认占用 8080 端口如果之前启动过一个实例没关干净或者别的程序占了 8080IDEA 控制台会直接报端口被占用。这时候最常见的错误操作是去改端口但改完发现还是报错——因为之前那个残留的 Tomcat 进程还在它占用的端口可能没变。我的处理办法是先搞清楚是谁占用了端口。命令行执行netstat -ano | findstr 8080输出结果里会有一个 PID 数字然后taskkill /PID 数字 /F直接杀掉。杀完之后再启动。如果你确实想改 Tomcat 使用的端口要去 IDEA 控制台日志里找 Using CATALINA_BASE 指向的那个 conf/server.xml把Connector port8080改成你想要的端口不要只改原解压目录里的文件。还有一个藏得很深的问题同一个项目被注册成多个 Tomcat 配置比如之前乱点创建了 Tomcat 9后来又建了 Tomcat 8两个配置共用同一个 Deployment 目录。启动 A 配置时发现 project 目录被占用其实是 B 配置的实例还开着。所以启动前多看一眼当前运行窗口有哪些实例养成一个项目只保留一个 Tomcat 配置习惯。5.2 中文乱码的三个产生位置乱码是 JavaWeb 项目的老大难问题。启动阶段遇到的乱码一般分散在三个位置控制台日志乱码、页面显示乱码、数据库存取中文乱码。控制台乱码根因在 IDEA 的日志输出编码和 Tomcat 的日志字符集不匹配。Tomcat 8.5 以上默认使用 UTF-8 输出日志但 IDEA 控制台可能使用 GBK 解析这时就在 IDEA 的 VM options 里加-Dfile.encodingUTF-8并在 Help 菜单的自定义 JVM 配置里加一条-Dconsole.encodingUTF-8。页面乱码来源通常是 JSP 页面声明和响应头编码不一致。每个 JSP 页面头部必须有% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %如果你的页面是 HTML 而非 JSP要在 head 标签里加meta charsetUTF-8。而且要注意静态 HTML 的 meta 声明和浏览器实际解码方式不是一个东西根源还是文件保存时的编码要统一成 UTF-8。数据库乱码则要分两层排查连接串有没有指定字符集数据库和表是不是 utf8mb4。连接串里加上characterEncodingutf8是最直接的办法jdbc.urljdbc:mysql://localhost:3306/tourism_db?useSSLfalsecharacterEncodingutf8这一条配置解决 90% 的数据库插入中文乱码问题。剩下的 10% 是 MySQL 服务端本身默认字符集不对这时需要检查 my.ini 里的character-set-server配置。5.3 数据库连接失败与驱动类加载失败很多同学反应代码一模一样别人能启动自己就是报ClassNotFoundException: com.mysql.jdbc.Driver或者Cannot create PoolableConnectionFactory。这类问题通常不是代码问题而是依赖和驱动的问题。ClassNotFoundException的根因是 MySQL 驱动 jar 没有进入最终部署的 WEB-INF/lib。明明 pom.xml 里写了依赖怎么就没了因为 IDEA 部署 WAR exploded 时如果 Maven 依赖没有被正确关联进 Artifact 的 lib 目录驱动就不会复制过去。检查办法启动前看一下项目的 External Libraries 里有没有 mysql-connector再看 Artifact 配置里 Output Layout 的 WEB-INF/lib 下有没有这个 jar。Cannot create PoolableConnectionFactory的根因一般是连接串或账号密码有问题。最常见的是 MySQL 8.x 驱动配了旧参数或者允许为空密码的账号被服务器拒绝。还有一点经常被忽略Druid 连接池对 URL 里的参数校验很严格个别字符写错它会直接报连接失败这时最有效的排查手段是写一个独立的 JDBC 连接测试类不用连接池用最原始的方式测一下public static void main(String[] args) throws Exception { Class.forName(com.mysql.jdbc.Driver); Connection conn DriverManager.getConnection( jdbc:mysql://localhost:3306/tourism_db?useSSLfalsecharacterEncodingutf8, root, 你的密码); System.out.println(conn); conn.close(); }如果这一步能连上说明问题在连接池配置连不上则检查账号密码、服务和端口。5.4 页面 404 与静态资源路径问题最后一个常碰到的坑是 404。项目明明启动了访问首页却 404大概率不是代码问题而是部署路径不对。如果你部署时设置的 Application context 是/tourism_web那你的文件如果放在webapp/index.html它对应的访问路径就是http://localhost:8080/tourism_web/index.html不能直接访问http://localhost:8080/index.html。还有更隐蔽的动态资源 404Servlet 的 WebServlet 注解路径写的是/routeServlet页面里表单请求写的却是routeServlet没有前导斜杠浏览器会相对于当前请求路径去解析结果跑到一个完全不对的地址上。我建议在项目启动期把所有 Servlet 映射路径和页面请求路径做成一个路径常量类集中管理或者在写请求路径时统一加斜杠。静态资源 404 则是另一个维度的坑。页面通过link hrefcss/style.css引用样式如果页面是在http://localhost:8080/tourism_web/login.html那么浏览器会把它解析成http://localhost:8080/tourism_web/css/style.css没问题。但如果你通过 Servlet 转发到了/WEB-INF/jsp/login.jsp页面地址显示的还是/tourism_web/loginServlet那么相对路径css/style.css就被解析成/tourism_web/css/style.css看似没问题但如果你用了base标签或者拼接了别的层级就会乱套。稳妥方案是在页面上用绝对上下文路径%request.getContextPath()%/css/style.css或者用 JSTL 的 c:url 标签处理。启动阶段可能感觉这样写麻烦但等你的页面多起来层级深了这个方法能帮你避免一整类 404 问题。说实话项目启动这件事考验的不是技术深度而是对细节的敏感度。我见过太多同学在这一步被端口、编码、路径、驱动轮番打击最后干脆放弃这很可惜。如果你正卡在某个启动问题上我的建议是不要同时改多个配置一次只改一个变量从控制台第一条报错开始逐条往后推大多数问题都能在三五个循环内定位。先把首页成功运行起来后面登录注册、线路展示这些模块都只是在此骨架上的正常扩展而已。