这一篇是Gradle入门系列里的第四篇,前几篇把Gradle的环境、基本命令和构建脚本语法聊完之后,很多人卡在了同一个地方:我到底怎么用它建一个能丢进Tomcat跑的web工程。网上一搜教程,要么是Spring Boot的starter模板,要么是Maven的一键原型,很少有一种干净的做法告诉你,Gradle里war包是怎么打出来的、目录结构该长什么样、Servlet为什么有时候能跑有时候又ClassNotFound。这篇就从一个空目录开始,不套IDE的项目模板,不碰Spring,纯手工用Gradle创建一个传统Servlet风格的web工程,最后打成war包丢进Tomcat验证运行。适合刚学完Gradle基本命令、想彻底搞清楚web工程构建逻辑的初学者,也适合用惯了Maven想对比两边差异的老开发。
这种做法的价值在于:当你看懂了war插件做的事,以后再碰Spring Boot的bootWar、Gretty的嵌入式容器,或者多模块拆包的场景,就不会觉得这些机制是玄学了。整篇内容全部来自我实际折腾过的记录,包括踩过的坑和查过的解决方案。
1. 先弄清:Gradle里的web工程和普通Java工程差在哪
1.1 约定目录结构的核心
Java工程从Maven时代就形成了一套默认目录约定,Gradle继承了这套思路。一个普通的Java工程,Gradle默认会认为你的源码在src/main/java,资源文件在src/main/resources,测试代码在src/test/java。如果你违反了这个约定,问题不是报错,而是编译出来的classes目录里什么都没有,但你还很难发现哪里错了。
web工程比普通Java工程多了一个关键目录:src/main/webapp。war插件打包时,会把整个webapp目录原封不动复制进war包的根路径,同时把编译后的classes和依赖jar包分别放进WEB-INF/classes和WEB-INF/lib。不理解这一点,就会遇到两个很典型的坑:
第一个坑是源码目录和webapp目录混在一起。有同学习惯把所有东西都塞到src/main/webapp下,Java代码写进WEB-INF/classes里,结果编译时Gradle根本找不到source,因为java插件只扫描src/main/java。
第二个坑是忘记建webapp目录。普通Java工程没有这个目录不会报错,但当你运行gradle war,最后生成的war包打开一看,里面只有WEB-INF,没有页面文件,因为整个webapp目录都不存在。
所以创建一个Gradle web工程,第一步不是改配置,而是把目录结构想清楚。最典型的单模块结构是这样:
myweb/ ├── settings.gradle ├── build.gradle ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/demo/ │ │ │ └── HelloServlet.java │ │ ├── resources/ │ │ │ └── application.properties │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ └── web.xml │ │ └── index.jsp │ └── test/ │ └── java/如果你是通过IDE新建Gradle项目,大概率只会生成src/main/java和src/main/resources,webapp目录得自己手动建。这个目录一旦缺了,war包不会报错,只是打出来的包里什么都没有,后面我会专门说怎么验证。
1.2 war插件和java插件的关系
Gradle有几十个官方插件,web工程用的核心就是war插件。从构建逻辑上看,war插件完全是在java插件的基础上叠buff:它继承了java插件所有的编译、资源处理、测试任务,然后又新增了war任务,并让war任务依赖compileJava和processResources。
所以在build.gradle里,你只需要声明:
plugins { id 'java' id 'war' }这两个插件一旦同时启用,Gradle会自动把war任务接入assemble和build任务链。也就是说,你执行gradle build时,最后除了jar包,还会额外生成一个war包。如果只需要war包,直接执行gradle war。
war插件还额外引入了几个依赖配置,其中最重要的就是providedCompile。在普通Java工程里,你想引入一个只在编译时用、不要打包进产物的依赖,一般用的是compileOnly。但是web工程运行在像Tomcat这样的容器里时,Servlet API这类依赖既不能打进WEB-INF/lib——因为容器自己带了同款类;又不能在运行时缺失——因为Servlet接口必须在编译期可见。war插件新增的providedCompile就是干这个的,它和compileOnly的共同点是都不进最终产物,区别在于providedCompile是web工程里更语义化的选择,而且在war任务的实际操作历史上,providedCompile还承担过某些IDE识别的特殊使命。
这个机制解释清楚,你就能理解下面这份依赖配置为什么这样写了:
dependencies { providedCompile 'javax.servlet:javax.servlet-api:4.0.1' providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3' }JSP API同理,Tomcat自带实现,你写JSP页面需要编译期引用它,但打包时得排除掉。
2. 动手前必须处理的三件事:镜像源、初始化方式、JVM配置
2.1 仓库源配置直接决定你入门体验
Gradle默认仓库指向Maven Central,这是全球共享的仓库,网络波动大,国内下载非常慢。很多教程里写repositories { mavenCentral() },初学阶段依赖少还好,一旦要拉Spring、Jackson几个大包,经常卡在Download进度条上半天不动。
解决方案是在repositories里多配置一个国内可用的镜像仓库地址。这不是什么高级技巧,但真的很影响体验。最简单的做法是把build.gradle开头写成这样:
repositories { maven { url 'https://maven.aliyun.com/repository/public' } mavenCentral() }注意,Gradle的依赖仓库解析是按声明顺序来的:先找镜像,镜像里没有再去Maven Central。这样既保速度,又保完整度。不要只配镜像不配mavenCentral,因为有些冷门依赖镜像里同步不全,最后报错会让你以为是自己坐标写错了。
还有一个容易忽略的点是插件仓库。如果你用的是plugins DSL这种新写法,Gradle下载插件本身也会走仓库。这时候插件的解析和数据依赖的解析是两条线,数据依赖看repositories块,插件依赖默认看Gradle Plugin Portal。为了统一管控,可以在settings.gradle里单独配置pluginManagement:
pluginManagement { repositories { maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } gradlePluginPortal() } }等你真的遇到“插件下不下来”的问题,就会想起这段配置的价值了。我个人的习惯是:新建工程第一件事先把settings.gradle和build.gradle的仓库写好,再写业务代码,不然等代码写了十几行突然卡在下载,心情会非常烦躁。
2.2 别急着用gradle init,手动建目录反而更快
Gradle自带gradle init命令,它能生成一个可运行的骨架工程。但实际用过几次就会发现,它默认生成的骨架是为通用场景设计的,未必符合web工程需要,比如默认没有webapp目录,生成的build.gradle里也没有war插件。你初始化完还得手动改半天,最后的工作量和手动创建差不了多少。
我更推荐手动建目录,因为整个过程就几条命令,非常快:
mkdir -p src/main/java mkdir -p src/main/resources mkdir -p src/main/webapp/WEB-INF mkdir -p src/test/java在项目根目录里执行完之后,目录结构就出来了。然后创建settings.gradle和build.gradle两个文件。
settings.gradle在单模块工程里主要用来声明项目名,它决定了输出war包的名字。假如你写:
rootProject.name = 'myweb'那最终的war包就会是myweb.war。如果没有settings.gradle,Gradle会默认用当前目录名作为项目名,在某些临时目录下就很容易打出奇怪名字的包,比如copied_directory_2025.war。所以这个文件别看只有一行,建议还是一开始就建好。
build.gradle则是最小配置:
plugins { id 'java' id 'war' id 'org.gretty' version '3.0.9' } group = 'com.example' version = '1.0.0' repositories { maven { url 'https://maven.aliyun.com/repository/public' } mavenCentral() } dependencies { providedCompile 'javax.servlet:javax.servlet-api:4.0.1' providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3' implementation 'org.slf4j:slf4j-api:1.7.36' testImplementation 'junit:junit:4.13.2' }这段配置里出现了Gretty插件,这是Gradle生态里很有名的一款嵌入式Web容器插件,它能在Gradle里启动Jetty或Tomcat,省去你手动拷贝war到容器的步骤。对一个以“创建普通web工程”为目的的项目来说,它不算必需品,但它能极大加速后续调试。新人如果把调试流程变成“改代码→gradle appRun→浏览器刷新”,效率会提高很多,这也是我建议加上的原因。
2.3 Gradle JVM和编码这两个隐形配置
还有一个特别容易忽略的是Gradle运行的JVM参数。Gradle自己跑在JVM上,它默认会去找环境变量JAVA_HOME指定的JDK。如果你的机器上装了多个JDK,而JAVA_HOME指向了Java 21,你的Gradle版本又比较老,可能直接启动报错。
更常见的问题是编码。web工程里全是中文页面和中文资源文件时,如果源码文件是UTF-8,而Gradle编译时用的系统默认编码不是UTF-8,编译后的class文件里中文注释和字符串会变成乱码,在页面上显示成问号。这是新手最容易莫名挨骂的点。解决办法是在build.gradle里把编码固化下来:
tasks.withType(JavaCompile) { options.encoding = 'UTF-8' }这个配置的作用范围是所有JavaCompile类型的任务,比单个compileJava里配置更省事,多模块工程里尤其重要。加上这句之后,compileJava、processResources、compileTestJava便统一使用UTF-8。
3. 从零实操:写代码、配web.xml、打war包、扔进Tomcat
3.1 一个能跑的最小Servlet和首页
目录建好、配置写好后,咱们写两段最简单的代码。先写一个Servlet,路由到/hello,返回一段纯文本:
package com.example.demo; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import java.io.IOException; public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain;charset=UTF-8"); resp.getWriter().write("Hello from Gradle web project."); } }再写一个最简单的主页文件src/main/webapp/index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Gradle Web Demo</title> </head> <body> <h2>Index Page</h2> </body> </html>接下来是web.xml。虽然Servlet 3.0之后支持注解式映射,这里还是用web.xml,因为它能直观地展示容器如何解析你的路由,而且很多老项目还在用这种风格。创建src/main/webapp/WEB-INF/web.xml:
<?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>Gradle Web Demo</display-name> <servlet> <servlet-name>hello</servlet-name> <servlet-class>com.example.demo.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>注意web.xml的schema版本和你的Servlet API依赖版本要匹配。你依赖的是Javax 4.0.1,对应web-app_4_0.xsd,这里如果写成3.1或其他,部分容器会给出schemaWarning,虽然不一定致命,但日志里多一行报警告容易误导人。
3.2 用gradle tasks和gradle war验证构建链路
写完后,在项目根目录下先跑一个命令:
gradle tasks输出列表里你会看到war任务挂在build组里,旁边还会有war这个分组。这说明war插件已经成功加载。如果这里看不到war相关任务,九成是build.gradle里的plugins块拼错了或插件没下载成功。
然后直接执行:
gradle war第一次运行时会下载依赖,输出里能看到类似“Downloading https://...javax.servlet-api-4.0.1.jar”的记录。依赖拉完后,构建输出末尾会出现BUILD SUCCESSFUL,然后你在build/libs目录下会看到myweb.war。
如果这一步失败,最常见的原因无外乎三种:插件坐标拼错、依赖坐标拼错、网络拉不下来。对应的报错信息里都会有明确提示,先看Build failed下面那一行,基本能定位到具体哪个模块。
验证war包内容这个环节,很多教程没提,但我认为非常重要。用下面命令看war包内部结构:
jar tf build/libs/myweb.war输出应该包含:
WEB-INF/ WEB-INF/classes/ WEB-INF/classes/com/example/demo/HelloServlet.class WEB-INF/lib/ WEB-INF/web.xml index.jsp看到这个结构你就知道war包打对了:页面文件在根路径下,Servlet编译产物在WEB-INF/classes下。如果WEB-INF/lib里出现了servlet-api.jar,说明providedCompile没生效,它把容器自带的API打了进去,这种war部署后很容易出现各种类冲突。
3.3 本地部署到Tomcat,跑通第一次请求
war包打出来就可以部署了。传统做法是把它复制到Tomcat的webapps目录下:
cp build/libs/myweb.war /path/to/tomcat/webapps/然后启动容器:
/path/to/tomcat/bin/startup.sh等待几秒后,浏览器访问http://localhost:8080/myweb/,能看到index.jsp页面。再访问/hello,就能看到Servlet输出的文本。
有一个细节:war包的文件名就是访问路径。你打的包叫myweb.war,那访问路径就是/myweb。如果你把war包改名成ROOT.war,访问路径就是根路径http://localhost:8080/。刚开始学的时候容易搞混,习惯在浏览器里输入一个不带项目名的地址却发现404,就是这个原因。
如果你加了Gretty插件,更简单的启动方式是在项目根目录执行:
gradle appRunGretty会默认启动内嵌的Tomcat,然后在浏览器访问http://localhost:8080/myweb/。改代码之后,不用重启容器,Gretty的appRun本身带热部署能力,它会自动重新加载变更的类。这在日常调试期比手工复制war包高好几个效率层次。
3.4 一个容易忽视的问题:Gradle守护进程与内存
新手做web工程时,经常遇到一个情况:改了几次代码后,感觉Gradle每次执行变慢了,甚至有时候直接卡死。这多半是Gradle守护进程在后台累积内存。Gradle默认会启用daemon,好处是第二次构建更快,坏处是如果你没配置JVM参数,Launcher进程默认最大堆可能是1GB甚至更高,加上IDE的JVM、Tomcat的JVM,你的开发机很容易内存吃紧。
解决办法不是禁用daemon,而是限制daemon的堆大小:
org.gradle.jvmargs=-Xmx512m写进gradle.properties文件,或者放在项目根目录的gradle.properties里。这个文件Gradle会自动读取。对于这种入门级web工程,256M到512M完全够用。
如果你发现某个版本号下的大依赖下载经常中断,还可以在gradle.properties里加两行:
org.gradle.wrapper.networkConnectTimeout=10000 org.gradle.wrapper.networkReadTimeout=30000这是控制网络超时时间,默认值有时候太短,在内网或慢速网络下容易出现莫名其妙的“Read timed out”。
4. 单模块web工程里的依赖管理细节
4.1 implementation与providedCompile如何配合
创建web工程时,新人最容易困惑的是dependencies里一堆配置名:api、implementation、compileOnly、providedCompile、runtimeOnly。它们的核心区别就是四个字:传递范围。
implementation到底意味着什么,我用一个例子讲:假设A模块的代码里写了B库的类,运行时不通过A拿到B,那B对A就是implementation。对web工程来说,你的Servlet API依赖应该是providedCompile,因为它由容器提供;你的业务逻辑依赖比如某个工具库,就应该是implementation。
在传统单模块web工程里,配置完整但克制是这样的:
dependencies { implementation 'com.google.guava:guava:31.1-jre' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' providedCompile 'javax.servlet:javax.servlet-api:4.0.1' providedCompile 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.3' testImplementation 'junit:junit:4.13.2' }guava和jackson会进入WEB-INF/lib,因为项目真正需要它们;servlet和jsp只在编译时可见,不会进包。这一点你检查war包时一眼就能看出来。
4.2 传统web工程里runtimeOnly的用法
还有一个不常用但很实用的配置runtimeOnly。它的意思是:编译期不需要这个依赖,类路径上不出现,但运行期必须把它放在WEB-INF/lib里。
最常见的场景是JSP编译器和数据库驱动。假如你的数据库驱动用的是某种第三方驱动,你代码里只依赖JDBC标准的接口,那么编译时不需要具体驱动,可以把它声明为runtimeOnly。这样打包时它会进WEB-INF/lib,但在编译阶段不会出现在classpath里,能避免开发时误用驱动自带的API。
runtimeOnly 'mysql:mysql-connector-java:8.0.33'打完war包检查WEB-INF/lib,你会看到mysql驱动在那里,这就对了。
4.3 刷新依赖和锁定版本的土办法
Gradle不会主动下载新的快照版本依赖,也不会手动替换已经缓存下来的损坏文件。如果你明明改了build.gradle里的依赖版本,但发现还是旧版本,多半是本地缓存没更新。这时先执行:
gradle --refresh-dependencies如果再不行,才考虑删掉用户目录下Gradle缓存的相关目录,但一般不需要做到这一步。
另外,多人协作时统一依赖版本避免某个人本地缓存新版本导致构建结果不一致,可以引入依赖锁定的思路。初级项目不一定要立刻用复杂的版本目录机制,但至少把版本号写在build.gradle的常量区:
ext { servletApiVersion = '4.0.1' guavaVersion = '31.1-jre' }然后在依赖里引用:
providedCompile "javax.servlet:javax.servlet-api:${servletApiVersion}"好处是项目里所有模块引用同一个版本时,改一处就行。等工程变大再考虑升级到版本目录或平台BOM,但初学阶段这个模式够了。
5. 常见问题与排查技巧实录
5.1 打出来的war包报404
这可以说是web工程入门阶段最高频的问题。页面访问出现404,90%的情况不是代码问题,而是war包部署路径和访问路径不一致。我遇到过一个场景:在IDE里直接运行Gradle,访问localhost:8080/项目名/能通,但换到命令行gradle war再部署,就404。原因就一个,IDE的Tomcat集成配置帮你在部署描述符里设置了applicationContext,项目名和war包名不一致而已。
排查思路很简单:第一步到Tomcat的webapps目录看war包是不是真的解压出来了、目录名长什么样;第二步看Tomcat的logs/catalina.out,里面会明确告诉你“Deployment of web application archive [...] has finished”或者报异常说context path无法确定。
5.2 Servlet编译找不到javax.servlet包
写Servlet类时,IDE里不报错,但命令行gradle compileJava报package javax.servlet does not exist。原因通常是:IDE默认的project SDK和命令行Gradle用的JDK不一致不是主要问题,主要问题是你把providedCompile写成了implementation,然后IDE会把这个依赖加到编译路径,但命令行下gradle编译时providedCompile依然在编译路径,所以这个说得通。
真正常见的错误是:你根本没有声明Servlet API依赖。Servlet类虽然在Tomcat运行环境里有,但Gradle编译时不知道Tomcat的存在,不声明就必然报错。解决办法就是补上:
providedCompile 'javax.servlet:javax.servlet-api:4.0.1'补上之后重新gradle compileJava,就不会报这个错了。如果还报,检查Tomcat版本和Servlet API版本是否匹配,比如Tomcat 10对应的是jakarta.servlet命名空间,不是javax.servlet。
5.3 页面能显示但Servlet返回404
页面访问正常,但访问/hello报404。多数情况是web.xml的servlet-mapping没生效,或者Servlet类没有被扫描到。用war插件时,Servlet类编译后应该在WEB-INF/classes/com/example/demo/目录下,而不是在war包的根目录。如果你打包时手动把.class文件放错位置,容器当然找不到。
另外检查一下web.xml里servlet-class写的是完整限定名。常见错误是写成HelloServlet,漏掉了com.example.demo前缀。
如果确认这些都正确,就直接看Tomcat日志。在Tomcat启动时,会打印它扫描到的Servlet映射。日志里能看到/hello被映射到哪个Servlet类。看到日志里的类名不对,就能定位到web.xml写错还是编译产物路径错了。
5.4 借助gradle dependencies排查依赖冲突
依赖冲突是web工程后期必然遇到的问题。比如你引用了两个库,它们都传递依赖了不同版本的commons-logging,最后war包里只有其中一个版本。这类问题在servlet类冲突上尤其烦人。
排查命令是:
gradle dependencies --configuration runtimeClasspath输出会以一种树形结构展示当前工程解析后的完整依赖链,每个库后面会标注是否发生版本冲突,Gradle默认会选择较高版本,但日志里会给出提示。这比你去war包目录里翻jar包要快得多。
还有一个更土但有效的手段:直接用jar tf命令检查war包里WEB-INF/lib下的所有jar文件,看看是不是存在两个名字相似的jar包。我之前排查过一次重复类问题,就是发现war包里有旧版的httpclient和新版的httpclient同时存在,瞬间就懂了问题根源。
5.5 页面中文乱码,不只是编码设置问题
写JSP时中文乱码是一个多环节问题,不只是编译编码。编译阶段的Java文件UTF-8配置解决了Java类里的字符串常量,但JSP页面的中文输出还依赖页面本身声明JSP页面编码、HTTP响应编码、浏览器解析编码三段一致。
我个人的经验是:JSP开头这么写,能避免九成乱码:
<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" %>同时在web.xml里给Servlet的响应设置:
resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8");如果页面本身是GBK编码,光把JSP pageEncoding改成UTF-8没有用,因为文件里那些中文的字节已经不是UTF-8编码。所以新建JSP文件时,直接在编辑器右下角确认编码是UTF-8,这是最省心的操作。
6. 从“能跑”到“跑得舒服”:兼顾调试与构建体验
6.1 用Wrapper固定Gradle版本
到这一步,你已经能创建、构建、打包、部署一个web工程了。但还有一个值得立马上手的工程规范:使用Gradle Wrapper。所谓Wrapper,就是工程内置一个脚本和配置文件,让所有成员在构建时都自动下载并固定版本的Gradle,而不是依赖各自机器上安装的全局Gradle。
启用方式很简单,在你的项目里执行:
gradle wrapper --gradle-version 8.5这个命令会生成gradlew和gradlew.bat脚本,以及gradle/wrapper目录。之后所有构建命令都改成:
./gradlew build而不是gradle build。好处非常实际:你团队里其他人拿到代码后不用自己装Gradle,直接跑gradlew脚本就会下载指定版本;而且这个版本被固化在properties文件里,不会出现“我本机Gradle 6能用,你本机Gradle 8编译不过”的尴尬。
6.2 增量构建到底有没有用
Gradle一直强调自己是增量构建系统,意思是:你的源码没变就不重新编译,输入变了才重跑相关任务。实践里,web工程资源文件经常变化,而Gradle对资源文件变化的检测机制是按输入输出快照来判断的。这意味着你只需要主要关注自己是否手动clean之后又build,只要不是频繁clean,第二次构建速度会明显快很多。
不要动不动就执行gradle clean。clean会删除build目录,强制所有任务重新执行。现代Gradle已经能很好地识别任务输出,除非确实遇到脏产物或缓存问题,否则用增量构建能省下大量时间。我见过有人每改一行代码都gradle clean build,然后抱怨构建慢,说白了就是这个习惯拖累的。
6.3 热部署调试的进阶思路
Gretty自带的热部署在入门阶段够用,它会在资源文件更改时自动reload。但如果你用的是传统的war包部署方式,改一个类就得重启Tomcat,调试效率低。还有一种轻巧做法是:把Tomcat的webapps下的应用目录直接指向你的构建产物目录,做法是在Tomcat的conf/server.xml里配置Context的docBase指向项目的build/libs/exploded目录。
当然这需要你先执行gradle war并解压war包。流程上略多一点步骤,但好处是改JSP页面后直接刷新浏览器就能看到效果。不过这个方案对新手来说偏复杂,我一般还是建议先用Gretty跑通本地体验,后面再按需研究容器内嵌。
6.4 我强烈建议新人都做的两个验证习惯
第一个习惯:每次打完war包,强制看一眼war包内容再部署。哪怕只是执行一句jar tf查看文件列表,也能避免把错误的产物发到测试环境。
第二个习惯:所有构建命令行操作都从项目根目录执行,不要进到子目录里跑gradle。Gradle会在当前目录向上寻找settings文件,如果你在子目录里跑,它找到的可能不是你的项目根目录,最后任务执行的上下文和你预期完全不同。这一类问题排查起来特别费劲,报的错又跟代码无关,非常影响心态。
这些习惯听起来琐碎,但确实是实操中反复踩出来的经验。把它写进自己的工作流里,比背多少遍理论知识都实在。
第7节我本来想展开讲讲怎么把web工程接入数据库、加日志框架等等,不过那是后面几篇要聊的内容了。这一篇的粒度定在“创建普通web工程并跑通war包”刚刚好,实战里的核心结论其实就几句话:war插件负责打包,providedCompile负责区分容器依赖,src/main/webapp是web文件的默认根目录。把这几个点牢牢记住,后面再演进到Spring Boot、多模块构建,思路都是通的。