news 2026/10/17 8:46:13

JavaWeb项目从创建到部署:Tomcat、Servlet、MySQL与war包发布全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb项目从创建到部署:Tomcat、Servlet、MySQL与war包发布全攻略

带新人的时候我经常发现一个奇怪的现象:明明代码逻辑都写对了,数据库也建好了,但项目到了“跑起来”这一步就卡壳。点开运行按钮,浏览器一片404;或者Tomcat启动了,页面却报500;再不然就是项目能开,数据库连接直接超时。这些问题说难不难,但排查起来特别绕,原因往往不是某一个环节错,而是从环境版本、项目结构、部署方式到数据库驱动这一整条链路里,有一个环节没对齐。

这篇文章我就用最笨的办法,把JavaWeb项目从创建、配置Servlet、连数据库到扔进Tomcat部署的完整过程拆开讲一遍。默认你用的是IntelliJ IDEA + Maven + MySQL + Tomcat这套最常见组合。适不适合你?只要你是刚学完Servlet、准备做第一个能增删改查的Web项目,或者被部署问题折磨了一下午还没搞定的人,这文章就是给你写的。

1. 版本组合选不对,后面全是坑:JDK、Tomcat、IDEA的配比逻辑

1.1 Tomcat 10之后Servlet命名空间发生了分水岭

很多人在网上找Servlet教程,复制了一段import javax.servlet.http.HttpServlet,结果在Tomcat 10里编译直接报错。这不是你代码的问题,是Tomcat版本导致的API迁移。

Tomcat 9及之前版本,Servlet规范里所有类的包名是javax.servlet开头;但从Tomcat 10开始,整个Servlet和WebSocket规范从Java EE迁移到了Jakarta EE,包名变成了jakarta.servlet。也就是说你把Tomcat 10下载回来,项目里所有import javax.servlet.*的地方都要改成jakarta.servlet.*,否则部署后就是ClassNotFoundException。

1.2 我建议的环境组合

如果你刚开始学习,不必追新版本,选一套社区验证最充分的组合,比用最新版本踩坑强得多。我目前最常给新人推荐的是下面这套:

组件推荐版本理由
JDKJDK 8 或 JDK 11生态最成熟,IDE和插件兼容性几乎没有坑
Tomcat8.5 或 9.0包名还是javax,网上大量教程直接可用
IDEA2021及以上社区版也支持JavaWeb基础开发
MySQL5.7 或 8.08.0用得最多,注意驱动类名差异
Maven3.6以上依赖管理必须

有些资料让你用Tomcat 10.1配JDK 17,这组合本身没问题,但一旦遇到老项目的Servlet代码,你要同时处理包名迁移和模块化带来的访问权限问题,学习成本翻倍。等你把javax这套链路走通了,再切到jakarta其实就是全局替换的事。

还有一个容易忽略的点:IDEA的Tomcat配置面板里有个JRE选项,它决定Tomcat启动时用哪个JDK。如果你的项目编译版本是JDK 17,但这里选了JDK 8,启动时会报UnsupportedClassVersionError。这个选项通常藏在Run Configuration的Environment里,报这个错时先看它。

1.3 环境变量的正确设置方式

Windows下要让Tomcat正确启动,JAVA_HOME必须精确指向JDK的安装根目录,而不是C:\Program Files\Java\jdk-17\bin这种带bin的路径。JRE_HOME不需要单独设置,Tomcat会自动找JAVA_HOME。

设置完后,验证方式是在命令行执行:

echo %JAVA_HOME% java -version

我见过有人配好了PATH里的java命令能识别,但JAVA_HOME是乱的,结果双击Tomcat的startup.bat窗口一闪而过。排查方法很简单,在startup.bat启动的黑色窗口消失前,右键编辑catalina.bat,在里面加一行pause命令,窗口就会停在报错信息上。

2. 新建JavaWeb项目的两条路线:IDEA原生工程与Maven war工程

2.1 路线一:IDEA原生JavaWeb工程添加Web骨架

如果你只是练手,不打算后续接Spring全家桶,可以直接在IDEA里新建一个普通Java项目,然后右键项目名,选择Add Framework Support,在弹出的窗口里勾选Web Application。IDEA会自动为你创建web目录,里面放着一个WEB-INF文件夹和web.xml配置文件。

这个方式最大的问题是依赖管理全靠手动。你下载了MySQL驱动jar包,必须右键jar包才能Add as Library,每次换机器都要重新来一遍,非常痛苦。我建议除非只是做五分钟的测试,否则不要用这种方式。

2.2 路线二:Maven构建war包工程(推荐)

Maven方式的标准项目结构长这样:

myweb/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ # 存放Servlet类、工具类 │ │ ├── resources/ # 存放配置文件 │ │ └── webapp/ # 存放HTML、JSP、web.xml

创建时在IDEA的New Project里选择Maven,勾选Create from archetype,选择maven-archetype-webapp。这个原型会自动生成一个最简单的index.jsp和web.xml,打包方式是war。

pom.xml中最核心的依赖就三个:

<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> </dependencies>

注意servlet-api的scope是provided。这意味着它只参与编译,不被打进war包。因为Tomcat自带了Servlet API,如果你强行把这个jar打进去,反而可能出现多个Servlet实现类冲突,报出一堆莫名其妙的NoSuchMethodError。这个细节我见过不少人栽过。

2.3 两种方式到底怎么选

我的判断标准很简单:只要你的项目以后可能会接数据库连接池、加过滤器、做登录验证,就直接Maven。Maven不仅帮你管理jar包,更重要的是它能统一项目的目录约定,让任何人在任何机器上打开项目都知道代码放哪、配置放哪、页面放哪。Maven默认这套目录结构,就是行业内最有共识的规矩。

还有一个好处:等你想学Spring Boot时,Maven的这些基础概念——依赖坐标、本地仓库、打包命令——全都能复用。你用IDEA原生Web项目学的那些,切换到Spring Boot后基本作废。

3. Servlet从写出来到跑起来:注解配置与URL映射

3.1 用@WebServlet还是web.xml?

有两种方式把Servlet暴露给Tomcat:一种是在类上标注@WebServlet("/hello"),另一种是在web.xml里配置servlet-mapping。

用注解的方式最简洁:

@WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); resp.getWriter().println("<h1>Hello JavaWeb</h1>"); } }

访问URL就是http://localhost:8080/项目名/hello。注意,这个URL里的项目名是Tomcat部署后的上下文路径,后面部署部分我会专门说明。

那web.xml还需要吗?如果你用的是Servlet 3.0以上规范(Tomcat 8.5就是),可以不写web.xml,甚至可以只放一个空壳:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> </web-app>

但我要提醒你,很多老项目和网上资料还是xml配置方式,你在web.xml里看到一个servlet-name标签,要能看懂它和url-pattern是怎么对应的。即使平时用注解,这个阅读能力也要有。

3.2 URL映射:404心结的根源

代理配置中一个常见错误是访问路径写错。假设你的上下文路径是/myweb,一个@WebServlet("/hello")的Servlet完整路径应该是:

http://localhost:8080/myweb/hello

少了myweb,或者多了一个斜杠,都会变成404。新手往往把IDEA运行窗口里的控制台输出当成参考,但Tomcat实际部署后的路径要以部署配置里Application context为准。

另一个容易忽略的坑是Servlet的URL映射不支持按后缀匹配多个斜杠。你写@WebServlet("/api/*")表示/api/下的所有请求都进这个Servlet;写@WebServlet("*.do")表示所有.do结尾的请求都进这个Servlet。这两种写法混在一起时,优先级规则是精确匹配优先于路径匹配,路径匹配优先于后缀匹配。这一块如果记不牢,调试的时候会走很多弯路。

4. 数据库接入:驱动、连接串、连接池的完整链路

4.1 驱动版本和类名是第一道坑

JavaWeb项目连MySQL,最土的写法就是JDBC。但JDBC的第一步Class.forName("com.mysql.jdbc.Driver")就埋了一个雷。

MySQL 5.x时代,驱动类名是com.mysql.jdbc.Driver;MySQL 8.0之后,驱动类名变成了com.mysql.cj.jdbc.Driver。你如果拿着MySQL 8的驱动,想着沿用旧习惯写老类名,运行时会直接报ClassNotFoundException: com.mysql.jdbc.Driver。

我建议的做法是:新项目统一用com.mysql.cj.jdbc.Driver。同时注意驱动版本的坐标,如果Maven仓库拉到的是mysql-connector-java8.0.x,对应的类名和URL格式都是新版风格。

4.2 连接串的每个参数都在解决一个具体问题

完整的MySQL 8连接串通常长这样:

String url = "jdbc:mysql://localhost:3306/testdb" + "?useUnicode=true" + "&characterEncoding=utf8" + "&useSSL=false" + "&serverTimezone=Asia/Shanghai" + "&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456";

拆开解释一下每个参数:

  • useUnicode=true&characterEncoding=utf8:保证中文能正常存进数据库。不设置的话,可能出现?问号乱码或者Incorrect string value报错。
  • useSSL=false:本地开发环境不需要SSL加密,设置成true反而可能因为证书问题导致连接失败。
  • serverTimezone=Asia/Shanghai:MySQL 8和驱动之间对时区的判断容易产生偏差,如果不设置,驱动会用JVM默认时区,经常报出The server time zone value 'Öйú±ê׼ʱ¼ä'的错误,这串乱码本质就是GBK和UTF-8解码错位显示的“中国标准时间”。
  • allowPublicKeyRetrieval=true:MySQL 8默认身份认证插件是caching_sha2_password,JDBC首次连接时需要从服务器获取公钥。不开启这个参数会看到Public Key Retrieval is not allowed的报错。

4.3 代码里该写裸JDBC还是连接池?

初学阶段,你确实可以用DriverManager.getConnection()一条连接走天下。但一旦项目里要多表查询、要在一个请求里连续做几次数据库操作,每次都新建和销毁连接,性能会明显劣化,因为TCP握手和MySQL认证的消耗是实打实的。

我建议哪怕只是一个学习项目,也把连接池引进来。这里不推荐你手写连接池,直接用Druid或者HikariCP。以Druid为例,Maven依赖坐标是com.alibaba:druid:1.2.20,使用方式非常直接:

public class DbUtil { private static DruidDataSource dataSource; static { dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); dataSource.setMaxActive(10); } public static Connection getConnection() throws Exception { return dataSource.getConnection(); } }

注意:连接池的testOnBorrow参数和validationQuery是配套的。如果你只设置maxActive就以为万事大吉,数据库重启后连接池里的旧连接会全部失效,项目会间歇性报CommunicationsException。正确的做法是加上一行dataSource.setTestWhileIdle(true)和dataSource.setValidationQuery("SELECT 1"),让连接池定期探测连接有效性。这个经验是我在项目偶发断连时排查了很久才确定的。

4.4 配置文件与编码问题

把数据库连接信息硬编码在Java类里,不属于正常的工程实践。时间久了,换数据库IP、换密码都要重新编译。我用db.properties文件来解决:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

然后在代码里加载:

Properties props = new Properties(); try (InputStream in = DbUtil.class.getClassLoader().getResourceAsStream("db.properties")) { props.load(in); }

这里有一个非常隐蔽的坑:getResourceAsStream返回的InputStream有可能是null,但不会立刻报NullPointerException,而是等到调用props.load(in)时才会炸。你排查的时候一看是NullPointerException,往往想不到是配置文件路径问题。所以加载配置时最好加一个判空:

if (in == null) { throw new RuntimeException("db.properties not found in classpath"); }

配置文件编码也是问题多发区。IDEA的默认全局编码建议统一设成UTF-8,否则properties文件里写中文密码或注释时,运行时会读出一堆乱码,连接失败的原因让你无从查起。

5. 部署到独立Tomcat:war包、运行配置与目录权限

5.1 两种部署姿态:war包和war exploded

Tomcat部署JavaWeb项目有几种方式,但对IDEA用户来说,最常见的是在Artifacts里配置一个Web Application: Exploded(解压目录),然后通过Run Configuration的Tomcat Server指向这个Artifacts。

这和你手动把war包丢进Tomcat的webapps目录有什么区别?IDEA的本地Tomcat运行配置,会把项目编译后的class文件、webapp下的静态资源和依赖jar自动组装好,然后以Tomcat标准目录结构加载它。改完代码后,IDE能智能地把变化的class热替换进去,开发效率高很多。

但有个问题:很多人首次配置时,Artifacts的Output Layout里没有WEB-INF/lib这个目录,结果代码明明引用了其他jar,运行时就是报ClassNotFoundException。手动解决的方法是,在Available Elements里找到你要的jar,右键选择Put into WEB-INF/lib。这个动作非常容易漏,我每次带人都要重复一遍。

5.2 IDEA里配置外部Tomcat的完整步骤

假设你的Tomcat安装在D:\apache-tomcat-9.0.xxx(Windows)或/usr/local/apache-tomcat-9.0.xxx(macOS/Linux)。配置路径是:

  1. 打开Run / Edit Configurations,左上角加号,选Tomcat Server -> Local。
  2. Server选项卡里,Application server右侧的Configure按钮,指向你Tomcat的安装目录。
  3. 切到Deployment选项卡,点加号选Artifact,选中你的war exploded包,设置Application context为/myweb。
  4. 回到Server选项卡,确认HTTP port是8080(或者你改过的端口)。

启动后IDEA会自动打开浏览器,你访问的URL末尾会自动拼上/myweb。

5.3 外部Tomcat的webapps部署方式

到了真正的部署环境(比如你买了一台云服务器,或者要在测试环境让同事访问),IDEA这种开发态部署就不适用了。此时的标准操作是:

在pom.xml里配置好<packaging>war</packaging>,执行Maven的package命令,在target目录下产生myweb.war,拷贝到Tomcat安装目录的webapps文件夹下,启动Tomcat。

Tomcat启动时自动完成解压,上下文路径就是war包文件名。如果你担心之前残留旧的解压目录,删除掉Webapps下同名目录再重启,避免增量残留。

cp target/myweb.war /path/to/tomcat/webapps/ bin/startup.sh

访问http://服务器IP:8080/myweb/hello,如果服务器开放了对应端口和防火墙规则,外网就能打到了。这一步最常见的问题是:你在本地跑通了,但服务器上端口没开放,或Tomcat绑定了127.0.0.1而不是0.0.0.0。检查conf/server.xml里的Connector标签,address属性没注掉的话,外部怎么都访问不了。

5.4 一个被很多人忽略的目录权限细节

Tomcat安装在Windows的Program Files目录下,或者Linux的/opt目录下,启动脚本可能因为权限不足没法创建日志文件和临时目录。表现现象是:Tomcat进程起来了,但日志目录空空的,部署进去的项目也不解压。

排查思路很简单:确认当前运行Tomcat的用户对安装目录有读写权限,尤其是logs、work、temp三个目录。Linux下直接给Tomcat目录授权到位比纠结组权限配置更省时间:

chown -R 当前用户:当前用户组 /path/to/tomcat

6. 部署后的高频报错:按这个顺序排查一次

6.1 端口被占用导致Tomcat起不来

双击startup.bat或点击IDEA运行,Tomcat窗口闪退,十有八九是8080端口被占用了。Windows下的排查命令:

netstat -ano | findstr 8080

查到占用进程的PID后,再去任务管理器里结束它。如果你不想结束,就改Tomcat端口。在conf/server.xml里找到Connector节点,把port="8080"改成8081。这一步改完后,IDEA的HTTP port也要同步改,否则两边不一致。

6.2 HTTP 404还是500,分别怎么查

404是“资源没找到”,500是“服务器内部错误”。两个状态码指向的方向完全不同。

404的来源通常是:

  • 上下文路径写错。你部署的是/myweb,访问却写/myweb2/hello。
  • Servlet映射写错。@WebServlet里的值少了一个斜杠。
  • war包没有解压成功。去Tomcat的webapps目录看一眼,有没有对应的文件夹。
  • 没部署任何artifact。IDEA的Deployment选项卡是空的,Tomcat起来后当然找不到项目。

500的来源通常是:

  • 代码里抛了异常。去Tomcat的logs目录翻catalina.out或localhost.log,按住Ctrl+F搜Exception。
  • 依赖jar缺失。启动时加载类失败,最佳排查方式是看异常栈的ClassNotFound和NoClassDefFoundError。

6.3 数据库连接失败的四大罪魁祸首

我整理一下最常遇到的数据库连接报错表格,方便你快速定位:

报错关键字根因解决方案
Cannot create JDBC driver of class驱动类名配错确认用com.mysql.cj.jdbc.Driver还是com.mysql.jdbc.Driver
Communications link failureMySQL没启动,或主机名/端口不通systemctl status mysql,或在代码里把localhost暂时改成127.0.0.1验证
Access denied for user用户名或密码错,或账号没有远程权限在MySQL客户端里重新授权对应host
Public Key Retrieval is not allowedMySQL 8的认证插件问题在URL参数里加allowPublicKeyRetrieval=true

6.4 中文乱码:从请求到响应的每一层都要统一

JSP页面、Servlet响应、数据库存储、Tomcat日志,每一层都可能出现中文乱码,但根源几乎都是编码不一致。我的建议是:

  • IDEA全局编码设为UTF-8。
  • 每个Servlet里设置resp.setContentType("text/html;charset=utf-8")。
  • JSP文件顶部写<%@ page contentType="text/html;charset=UTF-8" language="java" %>。
  • Tomcat的conf/server.xml给Connector加上URIEncoding="UTF-8",这样GET请求的URL中文参数不会乱。
  • MySQL表结构的CHARACTER SET统一用utf8mb4,比utf8支持更完整的字符集(比如表情符号)。

到这里,一个JavaWeb项目从创建到跑的完整链路就通了。我自己带人的时候,最后总让他们把整个流程不带笔记地重做一遍,目的不是考验记忆力,而是要让“配置环境、建Maven工程、写Servlet、配数据库、部署war包”这一串动作变成肌肉记忆。项目不是你用它的时候才去查一遍,而是你要能在十分钟内把它从一个空目录变成能跑的web应用。这套基础链路打扎实了,后面学SSM、Spring Boot,你会发现在这些框架的自动化配置之下运行的,还是你今天亲手搭起来的这套规则。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 8:45:21

鸿蒙RN实战:自封装Modal实现底部抽屉及手势动画

做跨端开发的同行应该都有感触&#xff0c;近两年最绕不开的话题就是鸿蒙系统接入React Native。尤其当业务方忽然丢过来一句“我们的RN应用要上鸿蒙”的时候&#xff0c;第一个要面对的问题是&#xff1a;那些在iOS和Android上跑得好好的组件&#xff0c;在鸿蒙上到底还能不能…

作者头像 李华
网站建设 2026/10/17 8:45:20

无人直播零基础实操:木偶云系统拆解与数据复盘

很多人第一次听到“无人直播”这四个字&#xff0c;第一反应基本都是&#xff1a;还能这么玩&#xff1f;屏幕里放着视频素材&#xff0c;人不用出镜&#xff0c;直播间依然有人看、有人问、甚至有人下单。我之前也是这么想的&#xff0c;直到自己用木偶云系统跑了好几场真实直…

作者头像 李华
网站建设 2026/10/17 8:45:19

客户端性能测试

一.什么是客户端性能测试 关于客户端性能测试&#xff0c;没有一个严格统一的定义。这里仅根据个人的理解来进行定义。 客户端性能测试是通过模拟用户在客户端实际使用过程中的各种操作场景&#xff1b;对应用程序在运行过程中的响应速度、资源消耗、运行稳定性等方面进行测试…

作者头像 李华
网站建设 2026/10/17 8:44:33

OpenCV实现极验滑动拼图识别的完整工程方案

1. 为什么极验滑动拼图是OpenCV实战的“试金石”我第一次在某电商后台系统里看到极验滑动验证码时&#xff0c;下意识点开开发者工具——不是为了绕过&#xff0c;而是被它背后那套视觉逻辑勾住了。它不像传统数字验证码那样靠OCR就能硬刚&#xff0c;而是把“人眼识别连续性”…

作者头像 李华
网站建设 2026/10/17 8:44:32

C#闭包陷阱详解:foreach已修复,for循环仍需警惕

闭包陷阱这四个字&#xff0c;凡是写过几年 C# 的人应该都不陌生。我第一次真正被它咬到&#xff0c;是在一次批量绑定按钮点击事件的时候&#xff1a;循环里写了十几行绑定代码&#xff0c;运行起来却发现每个按钮的回调拿到的都是最后一个数据。当时的我还以为是事件机制出了…

作者头像 李华
网站建设 2026/10/17 8:43:05

PyTorch实现TopK剪枝:原理、代码与实战踩坑记录

最近在调一个部署项目&#xff0c;原来跑得好好的模型&#xff0c;到了目标设备上一看&#xff0c;体量超了、延迟也压不下去。整个压缩方案里&#xff0c;最先落地的倒不是那些花哨的蒸馏、量化&#xff0c;而是最直观的一招——TopK剪枝。PyTorch里实现TopK剪枝并不复杂&…

作者头像 李华