JavaWeb保姆级教程:从IDEA创建项目到Tomcat部署全流程
2026/9/9 18:01:32 网站建设 项目流程

搞Java的,尤其是做传统Web开发或者维护老系统的同学,基本都绕不开JavaWeb和Tomcat这条线。很多学校的课程设计、企业内部的老项目、电商后台、报表系统,跑的都是这套东西。我前阵子帮一个刚入行的朋友从零搭环境、建项目、连数据库、部署上线,折腾了一圈下来,发现网上的教程要么太老,界面和流程跟IDEA 2024对不上,要么只讲了其中一段,从建项目到部署是割裂的,照着做很容易卡在半路。这篇就把整个流程拆开揉碎了写一遍,属于那种照着做就能跑通的保姆级教程,覆盖项目创建、Tomcat配置、数据库连接、打包部署,顺便把几个高频踩坑点也一并交代清楚。

先说清楚这篇适合谁看:零基础准备做JavaWeb课设的大学生、刚入职需要接手老项目的初级开发、以及想自己搭一套完整JavaWeb环境练手的自学党。如果你已经在用Spring Boot打天下,这篇里的手工Servlet和JSP项目可能略显复古,但Tomcat的部署原理和连接池的配置思路是通用的,看看也不亏。

1. 动手之前先搞明白:这几个东西到底是干嘛的

1.1 JavaWeb、Tomcat、数据库各司其职

很多人一开始就被三个概念搅糊涂了:JavaWeb是个什么东西,Tomcat是个什么角色,数据库又怎么和它扯上关系。其实用生活里的场景类比一下就清楚了。Tomcat可以理解成一个餐厅的店面,它负责接待客人(浏览器发来的HTTP请求),然后把客人点的菜(请求参数)传给后厨(你写的Servlet或者JSP代码)去处理。后厨做完菜,还要通过传菜窗口(JDBC)从仓库(MySQL数据库)里取食材(数据),最后把成品端到客人桌上(生成HTML响应返回浏览器)。

所以JavaWeb不是一个具体的软件,而是一套用Java语言开发Web应用的技术栈总称。Tomcat是这套技术栈里最常用的Web服务器,它实现了JavaEE(现在叫Jakarta EE)中的Servlet和JSP规范,负责把浏览器发来的HTTP请求转换成Java代码能处理的对象,再把Java代码生成的结果转换成HTTP响应发回去。数据库则负责持久化存储业务数据,Java代码通过JDBC这套标准接口来访问它,不同的数据库厂商提供各自的驱动实现,这样业务代码里就不需要关心底层数据库的具体差异。

理解了这层关系,你再看网上那一堆配置操作就不会觉得玄乎了。配置Tomcat,本质上是让IDE知道怎么把编译好的项目塞给Tomcat去跑;连接数据库,本质上是让Java代码能通过JDBC驱动找到数据库、通过认证、执行SQL。理解了本质,后面所有的操作都是“按规矩办事”而已。

1.2 版本选型怎么避坑(JDK、Tomcat、IDEA、MySQL)

版本选型是我最想强调的一部分,因为新手踩的坑里,有相当一部分不是操作问题,而是版本不兼容的问题。先列一个我目前推荐的组合,再解释为什么这么选。

组件推荐版本说明
JDK1.8 或 11JDK 8最稳定,很多老项目还在用;JDK 11对应Tomcat 9/10
Tomcat9.0.x 或 10.1.x9是老牌稳定版,10.1是Jakarta EE 10实现,二者包名不同
IDEA2024.1 及以上新版向导有变化,但手动补Web支持的办法通用
MySQL5.7 或 8.08.0默认认证插件是caching_sha2_password,驱动版本要注意
MySQL驱动5.1.49(对应5.7) / 8.0.33(对应8.0)驱动版本和数据库版本要对上

版本选型最大的坑在于Tomcat 10。Tomcat 10及更高版本把包名从javax.servlet换成了jakarta.servlet,这意味着很多老教程里的import javax.servlet.http.HttpServlet在Tomcat 10下会直接报“程序包不存在”。如果你用的是Tomcat 10,代码里必须写import jakarta.servlet.http.HttpServlet。这行改动虽然不大,但网上一搜一大把的老代码全是javax开头的,照抄就容易翻车。

另外,JDK版本和Tomcat版本也有对应关系。Tomcat 9.0要求JDK 8及以上,Tomcat 10.1要求JDK 11及以上。如果你机器上装的是JDK 17,那你最好选Tomcat 10.1,因为Tomcat 9虽然也能在JDK 17下跑,但有些反射相关的警告会刷屏,看着心烦。新手我建议直接JDK 8 + Tomcat 9 + MySQL 5.7,因为网上能找到的绝大多数JavaWeb教程都是这个组合,查资料、抄代码都不会有兼容性问题。

1.3 一个最小的请求是怎么跑通的

在动手操作之前,再花点时间理解请求的流转过程,这比直接点鼠标更重要。当你在浏览器输入http://localhost:8080/hello并回车之后,背后发生的事情是:

  1. 浏览器根据域名和端口,向本机的8080端口发起一个HTTP GET请求;
  2. Tomcat监听在8080端口,接收到请求后,根据URL中的上下文路径(也就是项目名)匹配到对应的Web应用;
  3. Tomcat把请求信息封装成HttpServletRequest对象,同时创建HttpServletResponse对象,然后调用web.xml或注解中映射好的Servlet的doGet方法;
  4. Servlet从HttpServletRequest里取出参数,调用业务代码,通过JDBC访问数据库取数;
  5. Servlet把结果写入HttpServletResponse,Tomcat把响应对象转换成HTTP响应报文返回给浏览器;
  6. 浏览器解析HTML,渲染页面。

这个链路理解清楚之后,排查问题就有一个清晰的思路:浏览器请求没到Tomcat?那是启动或端口的问题;请求到了Tomcat但页面404?那是上下文路径或Servlet映射的问题;页面出来了但数据不对?那是SQL或数据库连接的问题。把问题按链路节点去定位,远比漫无目的地乱试高效。

2. 从零创建JavaWeb项目:IDEA 2024实操全记录

2.1 新建项目:别再选错项目类型

IDEA 2024版的新建项目向导和之前的版本差别挺大,很多教程里的截图已经对不上了。我按下拉菜单路径一步步说清楚。

打开IDEA,点击New Project,这时候会看到左侧的选项列表。关键点来了:这里不要选Spring Boot,也不要选Jakarta EE,直接选Java或者Generators里的Maven Archetype。如果你只想用最简单的纯Java项目结构手工补全Web能力,选Java即可;如果你想一步到位用Maven管理依赖,选Maven Archetype,然后在Archetype列表里找到org.apache.maven.archetypes:maven-archetype-webapp。这个Archetype会直接生成标准的Web项目目录结构,包含src/main/webapp/WEB-INF/web.xml,省去很多手工操作。

我自己带新人的时候,更推荐先用Maven Archetype的方式创建,因为后面引入MySQL驱动、Druid连接池都要靠Maven管理依赖,手工下载jar包再导入,版本冲突和遗漏问题会让你怀疑人生。选好Archetype之后,填好GroupId和ArtifactId。GroupId一般写公司域名倒序,比如com.example,ArtifactId写项目名,比如javaweb-demo。Java版本选择要和本机JDK版本一致,我这边选的JDK 1.8。

2.2 手动补全JavaWeb骨架

如果你选的是Java类型的新建项目,创建出来的只是一个普通Java工程,没有src/main/webapp目录,也没有WEB-INF/web.xml。这时候需要手动补全,流程是:

  1. Ctrl+Alt+Shift+S打开项目结构(Project Structure);
  2. 左侧选择Modules,选中当前模块,点击上方的+号,选择Web
  3. 弹窗里会让你指定Web资源目录,把路径改成src/main/webapp
  4. 确认后,IDEA会为模块增加Web能力,并在webapp目录下生成WEB-INF/web.xml(如果你的版本没有自动生成,就自己手动建这个目录和文件);
  5. 还要把src/main/java目录标记为Sources Root,src/main/resources标记为Resources Root,这样IDEA才能正确编译和打包。

这个手动补Web支持的过程,是很多IDE版本迭代后依然通用的“保底方案”。不管向导怎么变,项目结构的要求是不变的,因此学会这一套,比背菜单路径更有价值。

对应的web.xml,如果是Servlet 4.0规范(Tomcat 9对应),最精简的版本长这样:

<?xml version="1.0" encoding="UTF-8"?> <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"> <display-name>javaweb-demo</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>

如果你用的是Tomcat 10,web.xml的命名空间要换成https://jakarta.ee/xml/ns/jakartaee,不能照抄旧的。这个细节非常容易踩坑,很多新手的Tomcat 10项目跑不起来,就是web.xml头写错了。

2.3 配置Tomcat,把项目跑起来

项目骨架就位后,先把一个最简单的页面跑起来验证环境。在webapp目录下新建一个index.jsp,内容随便写点东西,比如:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>JavaWeb Demo</title> </head> <body> <h2>Hello JavaWeb!</h2> </body> </html>

然后配置Tomcat运行环境。点击IDEA右上角的Add Configuration...,在弹窗左上角点+,往下翻找到Tomcat Server,选择Local。在Application server那里点Configure...,选择你本地Tomcat的解压目录。注意这里的Tomcat目录是解压后的根目录,比如D:\apache-tomcat-9.0.89,不要选到binconf子目录。

切到Deployment选项卡,点+,选择Artifact,然后选中javaweb-demo:war exploded。这个war exploded是“展开的war包”,也就是目录形式的部署,开发调试时用这种模式,改代码可以热更新,不用每次都重新打包。如果这里找不到Artifact,说明前面Web支持没有配置好,回到项目结构里检查Modules有没有Web Facet。

Application context这一项默认是/项目名,也可以改成/表示部署在根路径。我建议开发阶段就保留/项目名,因为以后部署到服务器时,这个上下文路径决定了URL怎么访问,尽早养成带路径访问的习惯。配置完成后,点击右上角的运行按钮,IDEA会启动Tomcat并自动部署项目。启动日志出现INFO: Server startup in [xxx] milliseconds,说明启动成功。打开浏览器访问http://localhost:8080/javaweb-demo/,看到Hello JavaWeb!就说明整个开发环境彻底跑通了。

2.4 顺带说说用Cursor创建Java项目

最近不少人问用Cursor能不能创建Java项目。Cursor本质上是VS Code套壳加AI助手,如果你非要用它写Java,流程是:先确认装了Extension Pack for Java扩展包,然后在终端里执行mvn archetype:generate,用Maven命令行生成项目骨架。或者更简单一点,用IDEA创建好项目后,再用Cursor打开项目目录,让它辅助写代码、改配置。

我的建议是:Java开发还是老老实实用IDEA,Cursor的AI能力可以通过IDEA自带的AI插件或者后端接入大模型的方案来弥补。编程工具选型,稳定性和生态优先,不要为了赶时髦把整个开发流程搞复杂。

3. 连接数据库:从JDBC到连接池

3.1 认识JDBC:Java和数据库之间的翻译官

JDBC(Java Database Connectivity)是Java提供的一套数据库访问标准接口。它定义了一套统一的方法,比如Connection(连接)、Statement(执行SQL)、ResultSet(查询结果),至于这套接口怎么实现,由各家数据库厂商的驱动jar包来负责。你可以把JDBC理解成USB接口标准,数据库是打印机,驱动是打印机驱动程序。只要支持USB标准,不管什么牌子的打印机都能插上就用;只要数据库提供了JDBC驱动,Java代码就能用同一套API去操作它。

所以核心结论就是:连接不同的数据库(MySQL、Oracle、PostgreSQL),Java业务代码的写法几乎一样,区别仅仅在于引入的驱动jar包和连接URL不同。这也是为什么很多人会说“会了JDBC,换数据库只是换驱动和URL的事”,话虽夸张,但基本属实。

3.2 引入MySQL驱动和Druid连接池

既然用了Maven,引入依赖就是编辑pom.xml的事。在<dependencies>标签里加上MySQL驱动和数据库连接池的依赖:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency>

注意,如果连接的是MySQL 5.7,驱动版本建议用5.1.49,不要直接用8.0.33,因为8.0驱动在连接5.7时偶发时区相关的警告,而且默认认证插件行为也不一样。如果连接的是MySQL 8.0,驱动版本必须用8.x,否则会报Public Key Retrieval is not allowed这种错误。

阿里巴巴的Druid连接池,是我在高并发和日常小项目中都用得比较顺手的选择。它不只是一个连接池,还自带监控、SQL防注入、慢查询日志等功能。对于课程设计和中小型项目来说,用Druid比直接手写DriverManager获取连接要规范得多,因为频繁地创建和销毁Connection耗时且浪费资源,连接池复用的思路能让程序在并发请求下的表现稳定很多。

3.3 数据源配置与实战

连接池的核心原理不复杂:启动时预先创建一批数据库连接放在池子里,请求来了就从池子里借一个连接用,用完了还回去,而不是关闭。这样省去了反复建立TCP连接、认证、断开的过程,对性能的提升非常明显。

src/main/resources目录下新建druid.properties配置文件:

driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/javaweb_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username=root password=123456 initialSize=5 maxActive=20 maxWait=3000

然后写一个工具类封装数据源获取逻辑:

package com.example.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class JdbcUtil { private static DataSource dataSource; static { try (InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("druid.properties")) { Properties props = new Properties(); props.load(in); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError("初始化数据源失败: " + e.getMessage()); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

写一个Servlet测试数据库连接,比如UserServlet.java

package com.example.servlet; import com.example.util.JdbcUtil; 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.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @WebServlet("/user/list") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter(); String sql = "SELECT id, name, age FROM t_user ORDER BY id"; try (Connection conn = JdbcUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { out.println("<table border='1'>"); out.println("<tr><th>ID</th><th>姓名</th><th>年龄</th></tr>"); while (rs.next()) { out.println("<tr>"); out.println("<td>" + rs.getInt("id") + "</td>"); out.println("<td>" + rs.getString("name") + "</td>"); out.println("<td>" + rs.getInt("age") + "</td>"); out.println("</tr>"); } out.println("</table>"); } catch (Exception e) { e.printStackTrace(); out.println("<h3>数据库查询出错: " + e.getMessage() + "</h3>"); } } }

注意这里用的是@WebServlet注解来注册Servlet,省去了在web.xml里逐个配置的繁琐。这是Servlet 3.0之后推荐的用法。

3.4 连接数据库的六个常见坑

坑一:驱动类找不到。报错ClassNotFoundException: com.mysql.jdbc.Driver,多半是依赖没引入或者没刷新。检查pom.xml里的依赖坐标,然后IDEA右侧Maven面板点刷新。另外,MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,不是老的com.mysql.jdbc.Driver,这个很容易写错。

坑二:时区错误。报错The server time zone value '�й���׼ʱ��' is unrecognized,是因为连接URL里没有指定时区。在url参数后面加上serverTimezone=Asia/Shanghai即可。

坑三:SSL连接警告或错误。连接URL里加上useSSL=false,开发环境没必要用SSL,省掉一堆告警。

坑四:Public Key Retrieval is not allowed。这个问题在MySQL 8.0 + 8.x驱动下很常见,原因是8.0默认认证插件是caching_sha2_password,需要先拿到服务器的公钥才能传输密码。解决方式是在连接URL加上allowPublicKeyRetrieval=true,或者把MySQL用户的认证插件改回mysql_native_password

坑五:中文乱码。这个涉及三处:连接URL里的characterEncoding=utf8、页面响应的ContentType、数据库表的字符集。口诀是“URL、页面、库表,三处统一UTF-8”。

坑六:连接池用Druid的DruidDataSourceFactory.createDataSource(props)时,配置文件里字段名写错导致解析异常。比如把maxActive拼成maxActive大小写不对,或者漏了driverClassName。配置文件字段名严格区分大小写,而且必须是Druid能识别的属性名。

4. 项目打包与部署:从开发机到服务器

4.1 两种部署方式怎么选

部署到Linux服务器是JavaWeb项目的最终归宿。部署方式上,常见的是两种:一种是直接把打过包的项目放到Tomcat的webapps目录下,启动Tomcat自动解压部署;另一种是配置Tomcat的conf/server.xml,在<Host>节点下加<Context>,指定项目路径映射到外部目录。

第一种方式最简单,把war包或整个项目目录扔进webapps,重启Tomcat就完事,适合课程设计、小型项目。第二种方式可以把项目放在Tomcat之外,好处是升级项目时不用动Tomcat安装目录,重装Tomcat也不会丢项目文件,适合稍微正规一点的环境。我个人的习惯是:小项目用第一种,省心;如果服务器上同时跑多个项目,或者有独立的发布目录,用第二种更灵活。

4.2 用IDEA打war包(2024版本细节)

在IDEA里打war包,先确保项目结构里配置了Web Artifact。打开Project Structure,点Artifacts,如果列表里没有,点+选择Web Application: Archive,名字默认是项目名:war。确认Output directory是你要存放war包的路径。

然后依次点击菜单Build -> Build Artifacts -> 项目名:war -> Build。构建完成后,到输出目录就能看到javaweb-demo.war

有一点要提醒:IDEA 2024把Build Artifacts的入口藏得稍微深了一点,一些魔改版本的IDEA可能在菜单里找不到这个选项。你可以在项目结构里直接把Artifacts的Output directory改到一个好找的目录,或者用Maven的方式执行package命令来打war包。如果你用的是Maven Archetype创建的项目,直接在IDEA右侧Maven面板双击package,然后在target目录下找war包,更直接。

Maven打包war前,记得在pom.xml里设置打包方式:

<packaging>war</packaging>

4.3 Linux服务器部署:Tomcat安装、上传、启动

服务器这块我以CentOS 7环境为例,其他发行版命令大同小异。基本流程是:

  1. 上传JDK和Tomcat的tar包到服务器,解压到指定目录,比如/usr/local/jdk8/usr/local/tomcat
  2. 配置环境变量JAVA_HOMECATALINA_HOME,并加入PATH
  3. 把war包上传到/usr/local/tomcat/webapps目录;
  4. 启动Tomcat,执行/usr/local/tomcat/bin/startup.sh
  5. 确认启动成功,访问http://服务器IP:8080/javaweb-demo/

启动后需要检查日志,看/usr/local/tomcat/logs/catalina.out的输出。最常犯的错误是没看日志就急着访问,结果报404或者连接拒绝,然后又怀疑Tomcat装坏了。其实Tomcat启动是否成功,日志里写得很清楚,养成先看日志再判断的习惯,排查问题会快很多。

4.4 Linux部署的三个坑:内存、端口、日志

第一个坑是内存溢出。默认JVM堆内存太小,项目稍微复杂一点就会出现OutOfMemoryError。解决办法是在Tomcat的bin目录下创建setenv.sh,写入:

export JAVA_HOME=/usr/local/jdk8 export JAVA_OPTS="-Xms512m -Xmx1024m -XX:MaxMetaspaceSize=256m"

-Xms是初始堆大小,-Xmx是最大堆大小。-Xms-Xmx设置成一样比较好,避免运行中频繁扩容。MaxMetaspaceSize限制元空间大小,防止类加载过多导致内存泄漏。

第二个坑是端口不通。Tomcat启动成功,但浏览器访问不到,先检查服务器防火墙:

firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --reload

如果用的是云服务器,还要去安全组规则里放行8080端口。这个步骤经常被忽略,很多人在本地跑得好好的,一上云就访问不了,99%是安全组没放端口。

第三个坑是日志文件疯狂增长。Tomcat默认的日志机制会把所有输出写到catalina.out,时间长了文件会变得巨大,磁盘空间告急。可以配置日志轮转,或者直接清空。开发环境的话,我建议在setenv.sh里调整一下日志级别,减少无用的DEBUG信息输出,生产环境则要配合logrotate做自动切割。

5. 常见问题排查实录

5.1 Tomcat启动慢、卡住不动怎么办

出现INFO: Deploying web application directory ...之后长时间没反应,大概率是在初始化日志或安全相关组件时卡住。一个非常经典的场景是:用JDK 8或JDK 9启动Tomcat,在Linux上会卡在SecureRandom这个随机数生成器上,因为它会阻塞等待系统熵池补充,而云主机或虚拟机环境熵池通常很小。

解决办法是在setenv.sh里加一行JVM参数:

export JAVA_OPTS="$JAVA_OPTS -Djava.security.egd=file:/dev/./urandom"

这个参数告诉JVM使用/dev/urandom来代替/dev/random,不走阻塞等待,启动速度会明显改善。

5.2 乱码问题全家桶

乱码是JavaWeb新手遇到的最多的一类问题,我这里按发生位置拆开来逐个说。

控制台乱码,一般是IDEA或Tomcat的日志编码问题。IDEA中,打开Help -> Edit Custom VM Options,加上-Dfile.encoding=UTF-8,然后重启IDEA。Tomcat方面,在bin/catalina.bat(Windows)或setenv.sh(Linux)里加上:

export JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8"

页面乱码,检查三点:web.xml或JSP页面声明的字符集要UTF-8;Servlet里resp.setContentType("text/html;charset=UTF-8");如果用了request.getParameter,还要加上req.setCharacterEncoding("UTF-8")(在读取参数之前设置)。

数据库乱码,检查连接URL里的characterEncoding=utf8,同时确认数据库表字符集是utf8mb4utf8mb4utf8能存的字符更全,推荐直接用utf8mb4

还有一个容易被忽略的点:HTML页面本身的文件编码。IDEA右下角可以看到当前文件的编码,如果文件本身是GBK编码但页面声明UTF-8,中文必乱。统一把所有源码文件设置成UTF-8编码。

5.3 IDEA启动后Tomcat生成一堆log文件

这是一个让很多人都头疼的问题。IDEA配置Tomcat后,每次运行项目都会在Tomcat的logs目录下追加日志,本地调试频繁,日志文件非常多。更烦的是tomcat9-stderr.logtomcat9-stdout.log这些文件会不断变大。

我的处理方式是在本地调试时,把Tomcat的日志级别调低,并且关闭不必要的日志输出。具体操作是修改conf/logging.properties,把org.apache.jasper.level设为WARNING,同时可以把localhost.logmanager.log去掉或降级。

如果只是想让IDEA运行时不刷那么多日志,可以在Run Configuration的VM options里加上:

-Djava.util.logging.config.file=D:/apache-tomcat-9.0.89/conf/logging.properties

让Tomcat使用指定配置文件。真正常跑项目的服务器,则建议用系统日志管理工具接管日志轮转,甚至直接把标准输出重定向到/dev/null再配合专门的日志框架,从源头控制文件增长。

5.4 Linux配置setenv.sh后用systemctl启动失败

这个坑我那年踩得印象特别深。服务器上配置了setenv.sh,里面设置了JAVA_OPTSJAVA_HOME,然后写了一个systemd服务单元来管理Tomcat,结果一执行systemctl start tomcat就失败,但直接执行startup.sh却没问题。

排查过程是这样的:先看journalctl -u tomcat的日志,发现报错是找不到JAVA_HOME或者环境变量没生效。原因在于systemd启动时不会加载/etc/profile里的环境变量,而startup.sh在交互式shell中能读到的环境变量在systemd环境下是不存在的。另外,如果在setenv.sh里用export设置的变量,在直接执行startup.sh时会被继承,但在systemd里,如果脚本的执行目录或执行用户不对,也可能读不到。

解决办法有两个。一是在tomcat.service文件里显式指定环境变量:

[Service] Environment=JAVA_HOME=/usr/local/jdk8 Environment=CATALINA_HOME=/usr/local/tomcat ExecStart=/usr/local/tomcat/bin/startup.sh

二是用systemctl edit tomcat添加覆盖配置,在[Service]段加上Environment。其实更标准的做法是用catalina.sh run作为ExecStart,让Tomcat在前台运行,由systemd托管进程生命周期,而不是用startup.sh这种后台启动方式。这两个方式之间的区别,对理解Linux服务管理非常有帮助。

5.5 nginx反代Tomcat,访问不到了怎么办

很多项目前面会挡一层Nginx做反向代理,静态资源由Nginx直接返回,动态请求转发给Tomcat。Nginx配置的核心是:

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; }

如果配好后访问Nginx的80端口出现404,第一反应不是怀疑Nginx配置,而是看上下文路径。假如你的项目部署在Tomcat的/javaweb-demo上下文中,而Nginx的location /直接proxy_passhttp://127.0.0.1:8080,那用户访问Nginx根路径时,请求转发到Tomcat是/路径,Tomcat返回404。

解决方案是两种:一是把项目的Application context改成/,让项目部署在Tomcat根路径;二是把Nginx的proxy_pass写成http://127.0.0.1:8080/javaweb-demo/,或者给location配上proxy_pass http://127.0.0.1:8080/xxx/。还要注意,如果location /后面proxy_pass的URL以路径结尾,Nginx会将匹配的URL替换为该路径,不写结尾斜杠则是原样拼接,这个细节不改的话,静态资源路径全都会错。

另外,如果只配置了一台Tomcat,Nginx动静分离的意义主要在静态资源缓存和访问控制上;如果配了多台Tomcat,还要考虑upstream负载均衡和session共享问题。这属于进阶话题,后面有机会再单独展开。

5.6 端口被占用与安全组

启动Tomcat时报Port 8080 required by Tomcat v9.0 Server at localhost is already in use,十有八九是有其他进程占用了8080端口。Linux上排查方式:

netstat -tlnp | grep 8080

找到占用端口的进程号,再kill -9干掉它。Windows上可以用netstat -ano | findstr 8080查到PID,再到任务管理器结束进程。

改端口也可以。修改Tomcat的conf/server.xml里的<Connector port="8080" .../>为其他端口,比如8081。改完端口后,要记得防火墙安全组都同步放行,否则又是“怎么访问不了”的老问题。我自己平时本地调试习惯用8080,到服务器上会把端口改成自定义端口,然后再用Nginx把80转发过来,既统一了对外访问入口,又减少了端口暴露面积。

写完这些,想起第一次教实习生配环境时的场景:一个Tomcat版本选错,导致所有Servlet都爆红,他盯着屏幕看了半小时没发现是javaxjakarta的差别。后来我把这段经历讲给每个新人听,他们都觉得这个坑“早该知道”。实际上,技术的每个坑都是这样,踩过一遍就长记性了。希望这篇文章能让你少踩几个,哪怕只踩一遍就能跑通,我这份保姆级的心意就算到位了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询