如何为 Java web 应用搭建适合多人协作的开发服务器?
建议每人维护一套独立环境,把配置纳入 Git 管理。对于这个学校项目,用 Docker Compose 管理 Tomcat 和 MySQL,在容器中完成 Maven 构建,就能满足大部分需求。Vagrant 也可以用,等到确实需要统一整套虚拟机操作系统时再引入。
日常开发不建议所有人共用同一个 Tomcat 和数据库。有人重启服务、部署尚未完成的代码或修改表结构,都可能影响其他成员。共享服务器更适合用来验收和演示。
先确定需要统一的内容
IDE 可以各自选择,团队需要统一的是以下内容:
| 内容 | 应如何管理 |
|---|---|
| JDK、Tomcat、MySQL | 在环境配置中指定版本 |
| Maven、构建插件和依赖 | 固定版本,提交构建配置 |
| 数据库结构 | 提交建表或迁移脚本 |
| 开发数据 | 提供可重复导入的少量测试数据 |
| 构建、启动和停止命令 | 写进 README,尽量保持一致 |
| 密码和个人配置 | 提供配置模板,实际值不提交 |
统一环境能减少差异,但不能解决所有问题。文件名大小写、字符编码、时区和数据库排序规则也可能影响结果。
注意 Servlet 与 Tomcat 的兼容性
如果课程示例使用 javax.servlet.*,需要选择兼容这一 API 的容器,例如 Tomcat 9。从 Tomcat 10 开始,相关规范 API 改为 jakarta.servlet.*,旧应用仅替换服务器还不能完成迁移。具体兼容关系可查阅 Tomcat 版本表和迁移说明。
下面以 JDK 17、Tomcat 10.1 和使用 jakarta.servlet.* 的 Maven WAR 项目为例。如果已有代码使用 javax.servlet.*,应先调整版本组合。
用 Docker Compose 建立可重复的环境
宿主机需要安装可用的容器运行环境、Docker Compose 和代码编辑工具。项目使用的 JDK、Maven、Tomcat 和 MySQL 可以全部放在容器里。
假设项目已经能通过 Maven 打包为 WAR,目录可以这样安排:
school-web/
├── pom.xml
├── src/
├── Dockerfile
├── compose.yaml
├── .dockerignore
├── .env.example
└── README.md
1. 让构建脱离 IDE
在 pom.xml 中声明 war 打包方式,配置 Java 编译目标,并指定依赖和构建插件的版本。Servlet API 依赖通常使用 provided,由 Tomcat 在运行时提供;应用还需要声明兼容的 MySQL Connector/J 依赖。
为方便后面复制 WAR,在现有 <build> 中设置:
<finalName>app</finalName>
这样预期的构建产物就是 target/app.war。这些配置统一交给 Maven 管理,避免每个人都在 IDE 中手动添加 JAR。
2. 在容器中构建 WAR,再部署到 Tomcat
创建 Dockerfile:
FROM maven:3-eclipse-temurin-17 AS build
WORKDIR /workspace
COPY pom.xml .
COPY src ./src
RUN mvn -B clean package
FROM tomcat:10.1-jdk17-temurin
COPY --from=build /workspace/target/app.war \
/usr/local/tomcat/webapps/ROOT.war
EXPOSE 8080
CMD ["catalina.sh", "run"]
这个示例适用于单模块项目。如果构建还依赖其他模块或项目配置文件,需要一并复制。
示例中的镜像标签会随维护更新。团队正式使用时,应将所有镜像固定为核实过的完整版本;要求更严格时,可以使用镜像 digest,并通过提交记录统一升级。对应标签可查阅 Maven 镜像说明和 Tomcat 镜像说明。
创建 .dockerignore,避免把无关文件发送给构建器:
.git
target
.env
.idea
*.iml
3. 配置 Tomcat 和 MySQL
创建 compose.yaml:
services:
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: school
MYSQL_USER: school
MYSQL_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD in .env}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?Set DB_ROOT_PASSWORD in .env}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test:
- CMD-SHELL
- >-
MYSQL_PWD="$${MYSQL_PASSWORD}"
mysql --protocol=TCP -h 127.0.0.1
-u "$${MYSQL_USER}" "$${MYSQL_DATABASE}"
-e "SELECT 1" >/dev/null
interval: 5s
timeout: 5s
retries: 30
start_period: 30s
web:
build: .
ports:
- "127.0.0.1:8080:8080"
environment:
DB_URL: jdbc:mysql://db:3306/school
DB_USER: school
DB_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD in .env}
depends_on:
db:
condition: service_healthy
volumes:
mysql-data:
- 应用通过
db:3306连接数据库。Compose 网络内可以使用服务名访问数据库,localhost则指向应用容器自身。具体寻址方式见 Compose 网络文档。 DB_URL、DB_USER和DB_PASSWORD是自行约定的应用配置名。Servlet 应用需要读取这些变量,例如通过System.getenv()配置连接池,Tomcat 不会自动完成这一步。- 普通
depends_on只保证启动顺序。示例将数据库健康检查与service_healthy配合使用,等待数据库能够接受查询后再启动应用。应用仍需处理运行期间的连接中断。两者的区别见 Compose 启动顺序文档。 - MySQL 没有发布宿主机端口,需要使用命令行客户端时,可以直接进入数据库容器。
创建 .env.example:
DB_PASSWORD=replace-with-a-local-development-password
DB_ROOT_PASSWORD=replace-with-another-local-password
成员首次使用时复制一份:
cp .env.example .env
修改本地密码,并把 .env 加入 .gitignore。示例通过环境变量传递开发凭据,生产环境的凭据管理需要另行设计。
4. 启动和更新应用
在项目目录执行:
docker compose up --build -d
docker compose logs -f web
Tomcat 启动并成功部署应用后,就可以通过 http://localhost:8080/ 访问对应页面或 Servlet 路径。ROOT.war 表示部署到根上下文,是否有首页取决于应用代码。
修改代码后,重新构建:
docker compose up --build -d web
这套基础流程容易理解和复现,但不会自动同步源码或热部署。等团队熟悉构建和部署过程后,再优化更新速度。
使用容器里的 MySQL 客户端:
docker compose exec db mysql -u school -p school
停止环境:
docker compose down
命名卷会保留数据库数据。docker compose down -v 会删除这里定义的数据库卷,只应在确认需要清空本地数据时使用。
数据库也需要随代码演进
除了统一 MySQL 版本,团队还需要保持表结构一致。项目初期可以提交建表 SQL 和测试数据;表结构开始持续变化后,可以使用 Flyway、Liquibase 等迁移工具记录每次修改,避免在群里通知大家手工执行 SQL。
MySQL 官方镜像会在首次初始化空数据目录时,执行 /docker-entrypoint-initdb.d 下的初始化脚本。已有数据库卷时,重启容器不会重新执行这些脚本,因此不能用它们处理后续迁移。初始化条件见 MySQL 镜像文档。
IDE 能否自动使用虚拟机或容器里的 Tomcat?
可以预先保存配置,具体能做到什么取决于 IDE、版本和插件。启动、部署和调试是三个不同的操作:
- 启动 Tomcat。
- 把 WAR 部署到 Tomcat。
- 连接 JVM 进行断点调试。
上面的 Compose 和 Dockerfile 已经负责前两项。IDE 可以只用来编辑代码,启动环境时无需重新添加服务器。
需要远程调试时,可以将以下配置合并到 web 服务中,保留原有数据库环境变量和 HTTP 端口映射:
command: ["catalina.sh", "jpda", "run"]
environment:
JPDA_ADDRESS: "*:5005"
JPDA_TRANSPORT: "dt_socket"
ports:
- "127.0.0.1:5005:5005"
然后在 IDE 中保存一个连接 localhost:5005 的远程 JVM 调试配置。Tomcat 的启动脚本支持这些 JPDA 设置,详见 catalina.sh。
本地调试源码应与已部署代码一致,否则断点位置可能不匹配。调试端口只在需要时开启,并限制在本机访问。
如果团队统一使用某个 IDE,可以提交它支持共享的运行配置,注意不要包含个人绝对路径。如果各自选择 IDE,就统一命令、端口和配置约定,每个人保存一次连接设置即可。启动虚拟机本身无法替任意 IDE 自动注册 Tomcat。
容器能解决构建和运行环境问题,但宿主机上的 Java IDE 要提供完整索引和编译检查,可能仍需项目 JDK。如果希望开发工具也使用容器内的 JDK,需要选择支持容器或远程开发的 IDE 方案。
什么时候值得使用 Vagrant?
如果课程要求学习 Linux 管理,或者团队需要统一完整的操作系统环境,Vagrant 就比较合适。可以按以下步骤配置:
- 选择支持成员操作系统、CPU 架构及虚拟化 provider 的基础 box。
- 在
Vagrantfile中固定 box 版本,配置项目共享目录和端口转发。 - 使用 provisioning 脚本安装指定工具,并完成首次配置。
- 让成员通过
vagrant up创建环境,通过vagrant ssh进入环境。
借助 Vagrant 的共享目录机制,成员可以在宿主机编辑代码,在虚拟机内编译和运行。
也可以在 Vagrant 虚拟机里运行 Docker Compose,由 Vagrant 管理 Linux 虚拟机,Compose 管理应用服务。这需要额外处理虚拟机资源和共享目录,还要配置容器到虚拟机、虚拟机到宿主机的端口映射。对于刚开始的 Servlet/JSP 项目,这些工作未必值得。
如果采用这一组合,建议虚拟机内只安装容器运行环境,Tomcat 和 MySQL 统一交给 Compose 管理,减少重复维护。
题目中的第三方 Vagrant 项目可以用来参考配置思路。采用前应检查基础系统、软件版本和 provider 支持情况,并实际核实是否适合当前成员的机器。
多人协作还需要哪些约定?
配好环境后,还需要做好以下工作:
- 使用 Git 协作,每个成员保留自己的工作副本,通过分支和代码审查合并修改。
- 在 README 中记录从新机器到首次成功访问应用的完整步骤,包括数据库初始化方法。
- 在 CI 中使用固定的构建环境执行编译和测试。需要真实数据库的集成测试,应单独准备数据库服务。
- 准备一套共享验收环境,部署明确的提交或构建产物,供团队检查集成结果。
如果成员电脑无法运行这些环境,也可以使用共享 Linux 服务器。每位成员都应有独立的工作目录和 Compose 项目,并使用不同端口;仅设置不同项目名,无法自动解决宿主机端口冲突。
开始时,先跑通克隆仓库、填写本地配置和执行一条启动命令的流程,再请另一位成员从空环境完整复现一次。过程中发现的手工步骤,都应补进配置或 README。
备注:内容仅供参考。
