前几天有个学完Java基础的朋友拿了篇三年前的教程来问我:为什么我打开IDEA 2024,里面根本没有Java Enterprise这个选项?很多老博客确实还停留在"新建项目→勾选Web Application"的流程上,但IDEA这几年界面和默认行为变了不少,照着旧教程走,第一步就卡住太正常了。这篇文章就围绕一条最核心的链路展开:用IDEA 2024新建JavaWeb项目、把项目部署到Tomcat、再用JSP/Servlet连接MySQL数据库。我会把踩过的坑、给同事排查过的经典问题全串进去,尽量让你照着走一遍就能跑通。适合刚学完Java基础、准备做第一个完整Web项目的同学,也适合项目启动不起来时翻回来查。
1. 先理清版本搭配:JDK、Tomcat、IDEA 2024到底哪几个能一起干活
1.1 为什么照着老教程做,却连项目都建不出来
很多旧教程第一步是"打开IDEA,File → New → Project,左边选Java Enterprise,勾选Web Application"。但在IDEA 2024里,这个入口早就变了。新版新建项目界面重做过好几次,Java Enterprise变成一个可选生成器,如果你装的是免费社区版,压根不会出现Java Enterprise入口,只有Ultimate专业版才有整套Web开发支持。
更麻烦的是,就算你装的不是社区版,新版IDEA也更倾向于让你用Maven、Gradle这类构建工具来生成项目。也就是说,现代IDEA的工作流默认是"构建工具管依赖和打包,IDEA管编码和调试",而不是"新建一个独立Web模块"。所以我不想让读者死记某一步,而是理解背后的路径:用普通Java项目加Maven,再给项目补上Web能力,最后用IDEA把Tomcat挂上去,这是一条兼容性最高、也不会被IDEA版本更新淘汰的路子。
1.2 Tomcat版本和JDK、Servlet包名的对应关系
先解决一个特别容易被忽略的点:Tomcat 9和Tomcat 10/11看起来一样,但对Java开发者来说差别非常大。Tomcat 9还沿用的是 javax.servlet.* 包名,Tomcat 10开始迁移到了 jakarta.servlet.*,这导致网上绝大多数老教程里的Servlet代码(import javax.servlet.http.HttpServlet)在Tomcat 10以上直接编译失败。
所以如果刚入门,我个人强烈建议先用Tomcat 9。一方面教程多、依赖好找,另一方面学习阶段不涉及新命名空间迁移这种历史包袱。下表是兼容组合。
| 使用场景 | JDK版本 | Tomcat版本 | Servlet规范包名 |
|---|---|---|---|
| 学习旧教程/经典项目 | JDK 8或11 | Tomcat 9.x | javax.servlet |
| 学习新规范/新项目 | JDK 11或17 | Tomcat 10.1.x | jakarta.servlet |
| 生产环境求稳 | JDK 8或11 | Tomcat 9.x | javax.servlet |
JDK和Tomcat的对应关系也要看仔细:Tomcat 9要求JDK 8以上,Tomcat 10.1要求JDK 11以上,Tomcat 11则要求JDK 17以上。我的建议很直接:学习阶段用JDK 11 + Tomcat 9,这个组合最不容易出幺蛾子。
1.3 Tomcat下载与JAVA_HOME、CATALINA_HOME的配置细节
下载Tomcat时记得选zip压缩包,不要选Windows Service Installer。zip版解压就能用,换版本也方便,不会有服务残留、目录权限这种破事。下载后得到一个类似 apache-tomcat-9.0.x 的目录,我习惯把它解压到一个没有空格的路径,比如 D:\dev\apache-tomcat-9.0.98,空格偶尔会带来一些自定义脚本上的小麻烦。
环境变量方面,JDK需要配 JAVA_HOME 指向JDK根目录,然后在 PATH 里加 %JAVA_HOME%\bin。Tomcat严格来说不配 CATALINA_HOME 也能通过双击 bin/startup.bat 启动,但为了在命令行里用 catalina.bat 命令,还是建议配一下 CATALINA_HOME 指向Tomcat根目录。
配置完验证也很简单,命令行执行 java -version 能看到JDK版本,执行 %CATALINA_HOME%\bin\catalina.bat version 能看到Tomcat版本。很多"装完了但启动失败"的问题,其实都是环境变量指向了错误的JDK版本,先用这两条命令确认,比瞎猜快得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零建项目:IDEA 2024 创建 Web 工程的具体路径
2.1 推荐路线:先建Maven项目,再补Web Framework
我不会推荐用IDEA的"Java Enterprise生成器"直接生成一个复杂模板,因为生成的中间件太多,新手根本分不清哪部分是干什么的。更稳妥的路子是:先建一个干净的Maven项目,然后手动给它加上Web支持,步骤不超过十步,且每一步你都清楚自己在干什么。
新建项目时,左侧选择Maven,不勾选archetype,填好GroupId和ArtifactId。GroupId一般写公司的反域名,比如 com.example,ArtifactId写项目名,比如 demo-web。创建之后你会看到一个标准的Maven目录:里面有 src/main/java、src/main/resources,但还没有 src/main/webapp 这个Web目录。
右侧项目根目录上右键 → Add Framework Support → 在弹窗里勾选 Web Application。勾选后IDEA会自动创建 src/main/webapp 目录,里面有一个 WEB-INF 文件夹和 index.jsp 页面。到这一步,你的项目才真正算是一个Web项目,可以开始写Servlet、JSP了。
有个细节:Add Framework Support之后,IDEA可能会弹窗问你是否要创建 web.xml,我建议点Yes。虽然Servlet 3.0之后可以用注解代替web.xml,但保留它,在配置项目初始化参数、配置欢迎页时会方便很多。后面讲Servlet时再展开。
2.2 main/java、webapp、WEB-INF 的职责,别再物理上放错
Maven项目创建后,别把页面随手扔进 src/main/java,也别把Servlet放进 webapp。开发阶段把这两块职责分清楚,能省下大量排查时间。
src/main/java:Java源码,放Servlet类、Service、DAO、实体类。src/main/resources:配置文件,如JDBC属性文件、log4j日志配置。src/main/webapp:Web资源根目录,相当于部署后整个应用的根。JSP页面、CSS、JS、图片都放这里。src/main/webapp/WEB-INF:一个受保护目录,客户端无法通过URL直接访问其中的文件。web.xml就放这个目录下,模板文件或权限受限的页面也可以放这里,只能通过服务器内部forward转发。
新手最容易犯的错误就是以为JSP页面要放进 WEB-INF 才正规,结果在浏览器里直接输路径访问,怎么输都404。记住:WEB-INF 下的页面不允许URL直接访问,它只服务端内部跳转。如果想让页面被正常请求,放在 webapp 根目录或它的子目录下。
2.3 pom.xml 里需要什么依赖,以及第一个 Servlet 为什么用注解
Maven项目最关键的就是在 pom.xml 里声明Servlet依赖。打开pom.xml,在 <dependencies> 节点里加入:
xml复制<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
scope 写成 provided 的意思是:编译和测试时需要这个jar,但打包运行时不用包含它,因为Tomcat本身已经提供Servlet实现。如果你漏写provided,打出的war包会带着一份servlet-api.jar,部署到Tomcat后容易出现类冲突。
接下来写第一个Servlet。在 src/main/java 下创建一个类,比如 HelloServlet,继承 HttpServlet,重写 doGet 方法,并加上注解:
java复制@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>Hello JavaWeb</h1>");
}
}
有了 @WebServlet("/hello"),就不需要在web.xml里配置Servlet映射了。启动后访问 http://localhost:8080/hello 就能看到效果。我前面说保留web.xml,但不代表每个Servlet都要往里面登记,注解在开发阶段完全够用,web.xml主要负责全局配置。
3. 部署到Tomcat:Artifact、war exploded 和 Application context
3.1 Artifact到底是什么,war和war exploded怎么选
IDEA里配置Tomcat时,一定会遇到Artifact这个词,新手很容易被绕晕。简单理解,Artifact是"你的项目经过构建之后,要被放进容器的产物"。对Web项目来说,产物有两种形态:war包和war exploded。
war是一个压缩包,类似zip。你把war包丢进Tomcat的webapps目录,Tomcat会自动解压并部署。war exploded则是一个展开后的目录,里面就是完整的文件夹结构,IDE可以把资源直接映射给Tomcat,不需要压缩、解压那一步。
开发阶段我几乎永远选war exploded,因为改一个JSP或一个类,刷新浏览器就能看到效果,不用每改一次就重新打包。如果选war,每次改动都要重新Build Artifact,效率格外低。生产部署才是war包的活跃场景。
| 对比项 | war | war exploded |
|---|---|---|
| 形态 | 单个压缩包 | 展开的目录 |
| 构建速度 | 每次要打压缩包 | 增量更新,快 |
| 调试 | 不友好 | 适合开发调试 |
| 部署方式 | 拷贝到webapps | IDE直接映射到Tomcat |
3.2 IDEA里挂载Tomcat Server:从Edit Configurations到热部署
在IDEA中配置Tomcat,路径是:Run → Edit Configurations → 左上角加号 → Tomcat Server → Local。注意,这里选Local,不是TomcatEE,TomcatEE是Java EE规范集成服务器的选项,本地部署不需要。
填好Tomcat Server的配置后,会看到几个关键区域。
第一个是 Deployment(部署)页签。这里需要把项目Artifact添加进去,点右上角加号 → Artifact → 选中你的项目。如果之前是war形态,这里会出现 xxx:war 和 xxx:war exploded 两种,选择exploded。下面是 Application context,这个值决定浏览器用哪个路径访问你的项目。默认是 /项目名,例如 http://localhost:8080/项目名/。如果想直接用根路径访问,就把Application context改成 /。
第二个是 Server 页签,里面有两个跟热部署相关的选项:On Update Action 和 On Frame Deactivation。On Update Action 是在你手动点更新按钮时的行为,建议选 Redeploy;On Frame Deactivation 是IDEA从IDE窗口切走时是否自动更新,建议选 Update classes and resources。后者最常用,因为每次从IDEA切到浏览器时,静态资源和JSP改动会自动同步,小幅度类改动也能生效。但如果改了Servlet类的结构,比如新增了方法,还是需要手动Redeploy,因为Tomcat加载类的方式决定了它不能无限热替换。
之前有同学问过"为什么改了代码刷新页面还是旧内容",大多数不是配置问题,而是项目根本没启用Update classes and resources,或者改动的是Java类但不支持热加载。这时候别纠结,直接Ctrl+F10手动更新,或重启Tomcat。
3.3 脱离IDEA的独立部署:war包的生产级流程
如果到了真正部署上线的阶段,没人会一直开着IDEA去调试。独立部署的标准流程是:在项目中执行 mvn clean package,在 target 目录下拿到一个war包,把它复制到Tomcat的 webapps 目录下,再启动Tomcat,它就会自动解压部署。
这里有个对菜鸟很不友好的细节:war包的文件名就是访问路径。假如 target 下生成的是 demo-web.war,访问路径就是 http://ip:8080/demo-web/。想让访问路径变短,要么改打包时的finalName,要么把war包改名再扔进去。改war包名最简单,直接把 demo-web.war 改成 ROOT.war,这样Tomcat会把应用部署为根路径,访问 http://ip:8080/ 就是项目首页。
Windows下可以用 mvn clean package -DskipTests 跳过测试,Linux下同样。生产环境如果担心8080端口被人直接扫到,可以在前面套一层nginx反向代理,访问80端口再转发到Tomcat,这个我在第五节再写。
4. 连接MySQL:驱动jar、连接串和第一次查询
4.1 驱动jar该放哪:WEB-INF/lib 和 Tomcat/lib 的差别
JavaWeb连数据库,本质上是往classpath里加入MySQL驱动。但"放驱动"这个动作有两个位置,放错地方会引发各种隐藏问题。
第一个位置是项目的 WEB-INF/lib,也就是把mysql驱动jar复制到 src/main/webapp/WEB-INF/lib 目录下。这个位置的好处是驱动会随着项目一起打包,部署到哪都自带依赖,多个应用之间不会互相干扰。第二个位置是Tomcat的 lib 目录,放进去后所有部署在这个Tomcat上的应用都能共享这个驱动。
我强烈建议把驱动放进web项目的 WEB-INF/lib。原因很简单:Tomcat/lib里的jar对多个应用是共享的,一旦某天需要升级驱动版本,会影响所有应用;而项目自带的jar,升级只动当前项目,其他应用无感知。这个原则不仅适用于数据库驱动,凡是第三方jar都应该优先跟着项目走,只用 provided 作用域让容器提供Servlet/HTTP那类基础实现。
MySQL驱动jar的下载,最新版本叫 mysql-connector-j-8.x.x.jar,老版本叫 mysql-connector-java-8.0.33.jar。如果你用的是MySQL 8.0以上,选8.x驱动;如果还在用MySQL 5.7,选5.1.49之类的老驱动更稳。驱动版本和数据库版本不匹配的问题,很多人遇到过,通常会报通信链路异常或找不到驱动类。
4.2 连接URL里的参数为什么一个都不能少
写JDBC连接数据库,核心就四步:加载驱动、建立连接、执行SQL、关闭资源。拿MySQL 8举例,代码骨架如下:
java复制Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8";
String username = "root";
String password = "123456";
try (Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name FROM user")) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " - " + rs.getString("name"));
}
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
几个重点需要展开解释一下。
第一是 Class.forName("com.mysql.cj.jdbc.Driver") 这一行。在MySQL 8这个驱动里,类的全限定名是 com.mysql.cj.jdbc.Driver,不是在5.x时代常见的 com.mysql.jdbc.Driver。用老包名在MySQL 8驱动下会直接报 ClassNotFoundException。
第二是连接URL后面的参数。serverTimezone=Asia/Shanghai 是MySQL 8连接时经常会需要的,因为驱动跟数据库协商时区,不指定可能报 The server time zone value '�й���ʱ��' is unrecognized,这种乱码报错其实不是编码问题,就是时区没配对。useSSL=false 是因为本地开发一般没有配置SSL证书,关闭能减少警告,生产环境如果走内部网络也可以关闭,走公网则建议开启。characterEncoding=utf8 是告诉服务器用UTF-8传递字符,防止中文乱码。
第三是用try-with-resources自动关闭Connection、Statement、ResultSet。数据库连接是有限资源,不关闭的话,运行一段时间后会发现连接池被耗尽,应用卡死或报 Too many connections。用try-with-resources是最省心的方式,方法执行完自动释放。
4.3 偷个懒:用IDEA Database面板连库并生成实体类
热搜里有个词叫"idea直接连接数据库自动生成实例对象",这个IDEA自带功能确实好用。在IDEA右侧打开Database面板,点加号 → Data Source → MySQL,弹出连接配置窗口。
填主机 localhost、端口 3306、数据库名、用户名和密码。首次连接时IDEA会提示下载驱动,等它下载完再点Test Connection,显示成功就搞定。
连成功后,Database面板里会列出所有表。右键某张表,菜单里有Generate POJOs,IDEA能根据表结构自动生成Java实体类。老版本可能弹一个窗口让你填包名和输出目录,填好后点Go,实体类就出现在项目里了,每个字段对应一个Java属性。
不过用这个功能时要注意,点击生成之前看清楚IDE的选项。有的IDEA版本在生成时允许勾选Lombok、MyBatis Plus等框架注解,如果当前项目还是JSP/Servlet纯JDBC阶段,不要勾这些,否则生成的类依赖Lombok或持久层框架,自己还得补一堆配置。纯项目就生成最简单的POJO,字段类型对应即可。
5. 高频问题的排查链路:404、乱码、端口占用和日志
5.1 启动后访问404的三层排查法
Tomcat启动成功,但浏览器一直404,这是JavaWeb新手的常见难题。遇到这种问题,我按下面三层顺序排查,基本不会漏。
第一层看Tomcat是否真的启动成功。看启动日志里有没有 Server startup in [xxx] milliseconds,有这行才代表部署完成。如果启动日志最后是 Exception 或 ERROR,先解决异常再看别的。
第二层看访问路径和Application context是否匹配。在IDEA配置Tomcat时,Application context会影响URL。比如context是 /demo,那页面在 webapp/index.jsp 时访问 http://localhost:8080/demo/index.jsp;如果context是 /,则访问 http://localhost:8080/index.jsp。很多时候404不是项目坏了,就是访问路径少了前缀或多了前缀。
第三层看页面和Servlet有没有放错目录。JSP放在 webapp 下可以直接URL访问;放在 WEB-INF 下不能直接访问,只能通过Servlet内部forward跳转。Servlet的注解路径比如 @WebServlet("/hello"),访问时要在应用路径之后加上 /hello,比如 http://localhost:8080/demo/hello,漏了应用路径客户端也会报404。
5.2 控制台乱码和请求参数乱码的处理顺序
乱码问题是Tomcat相关搜索里常年排前列的。先说控制台输出乱码:启动Tomcat时IDEA控制台出现一堆中文乱码,大多是Tomcat日志的编码问题。处理顺序是:先改Tomcat conf 目录下 logging.properties,找到所有 ConsoleHandler.encoding 的地方,改成 UTF-8 或 GBK,Win环境我一般改为UTF-8;如果还不行,打开IDEA里 Help → Edit Custom VM Options,添加 -Dfile.encoding=UTF-8,重启IDEA。
再说请求参数乱码。POST请求中文乱码时,在Servlet开头加 request.setCharacterEncoding("UTF-8");GET请求乱码时,检查Tomcat conf/server.xml 里Connector配置,给8080端口那行加上 URIEncoding="UTF-8"。响应中文乱码时,设置 response.setContentType("text/html;charset=UTF-8"),JSP页面则确认 pageEncoding 和 contentType 都是UTF-8。
说实话,乱码问题的根子就是两端编码不一致。浏览器发送的编码、Tomcat接收的编码、Java处理时用的编码、页面声明的编码,四个环节必须一致,不是改了哪一处就能全部解决。
5.3 端口占用和IDEA生成一堆log文件
Tomcat默认端口是8080,如果启动时提示 Port 8080 was already in use,说明端口被其他程序占用了。Windows下执行 netstat -ano | findstr :8080,看到占用进程的PID,然后 taskkill /PID 对应PID /F 结束它。Linux下用 lsof -i:8080 查PID,再 kill -9 PID。如果这个端口一直有应用要用,也可以直接改Tomcat的 conf/server.xml,把8080改成比如8081。
另一个高频困扰是"IDEA启动Tomcat生成一堆log文件"。这些日志在系统临时目录下,比如IDEA每个run configuration都会往临时路径写 catalina.*.log。开发时感觉文件多很正常,解决方案有两个:一个是项目正常运行后,定期清理临时目录;另一个是调整Tomcat的 logging.properties,把不需要的输出级别调高。真正要警惕的是线上 logs/catalina.out 越来越大,应用日志务必配上按天切分或按大小切分,别让一个日志文件撑爆磁盘。
5.4 外部访问:nginx反向代理Tomcat的简单写法
线上部署后,不想让用户直接看到Tomcat的8080端口,也比较担心CPU和内存直接暴露给外网,通常会在Tomcat之前加一层nginx反向代理。核心配置非常简单,在nginx的server块里加一段:
nginx复制server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这样用户访问80端口,nginx把请求转发给本机的8080。加了 proxy_set_header 那几个头,Tomcat里的应用才能拿到真实客户端IP和原Host,不然日志里看到的IP全是127.0.0.1。如果项目部署在根路径ROOT.war,直接这样转发即可;如果项目路径是 /demo,在location里改成 location /demo/ { proxy_pass http://127.0.0.1:8080; },注意proxy_pass末尾有没有斜杠会改变路径拼接方式,这块配置改完要重启nginx验证。
最后分享一个我自己的习惯。新建JavaWeb项目时,我会先把MySQL驱动jar放好、把Tomcat挂载配置好,再写任何业务代码。先跑通一次"项目能起来、能连上库"的最小闭环,再往上加功能。因为很多方案卡住,不是业务逻辑复杂,而是最底层的基础链路不熟悉。你可以用这个顺序试试,遇到问题也好定位:先验证Tomcat能起来,再验证访问页面,最后验证数据库查询,逐层推进比一次全做完再报错要高效得多。
