一起学Java(18)-[配置篇]一个诡异(有趣)的Gradle Wrapper问题
在研究JUnit使用的时候,遇到了一个有趣的Gradle Wrapper使用问题,经过若干天的研究,终于找到并解决了问题,记录分享如下,这也是我们一起学Java过程中遇到的问题。
一、什么是Gradle Wrapper
Gradle Wrapper 是一个工具,简称 “Wrapper”,用于确保在不同环境中都可以使用同一版本的 Gradle 构建项目。它主要由一组脚本(gradlew
和 gradlew.bat
)和一些配置文件组成。这些文件存放在项目中,可以自动下载并使用指定的 Gradle 版本来构建项目,而不需要系统中预先安装 Gradle。
(一)Gradle Wrapper 的应用场景
项目的一致性构建环境:在团队协作中,确保所有开发者和构建服务器使用相同版本的 Gradle,避免由于不同 Gradle 版本而导致的构建错误和不一致问题。
便于持续集成和自动化构建:在持续集成环境中,构建过程需要自动化执行。通过使用 Gradle Wrapper,构建服务器可以自动下载并使用指定的 Gradle 版本,而不必手动安装,简化了构建环境配置。
提高项目的可移植性:Gradle Wrapper 可以让项目在没有预装 Gradle 的环境中直接运行构建命令,如
./gradlew build
,从而方便在不同的开发和生产环境中快速构建项目。便于控制不同项目的 Gradle 版本:对于同时维护多个项目的情况,Gradle Wrapper 可以确保每个项目使用自己指定的 Gradle 版本,避免由于版本不兼容而引发的构建错误。
(二)Gradle Wrapper 的原理
Gradle Wrapper 本质上是一个用于下载并执行特定 Gradle 版本的工具。它包含几个关键文件:
gradlew
和gradlew.bat
:这是可执行脚本,分别用于 Linux/Mac 和 Windows 系统。这些脚本会检查指定版本的 Gradle 是否已经下载,若没有则自动下载并执行该版本的 Gradle。gradle/wrapper/gradle-wrapper.properties
:此配置文件指定了 Gradle Wrapper 使用的 Gradle 版本和下载地址。构建时,Wrapper 会读取这个文件,从指定的 URL 下载对应版本的 Gradle。gradle/wrapper/gradle-wrapper.jar
:这是一个小型的 Java 程序,负责从网络下载指定版本的 Gradle。
工作逻辑如下:
- 当执行
./gradlew <task>
时,脚本会先检查本地目录中是否存在指定版本的 Gradle。 - 如果指定版本的 Gradle 不存在,Wrapper 会通过
gradle-wrapper.jar
从配置文件中指定的 URL 下载该版本。 - 下载完成后,Wrapper 调用该版本的 Gradle 执行构建任务。
(三)Gradle Wrapper 的使用方法
- 生成 Wrapper:可以通过在项目目录下执行
gradle wrapper
命令来生成 Wrapper 文件。这会创建gradlew
、gradlew.bat
脚本文件以及gradle/wrapper
文件夹中的相关文件。 配置 Gradle 版本:在
gradle-wrapper.properties
文件中指定所需的 Gradle 版本,例如:distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
。- 运行构建命令:之后可以通过
./gradlew build
直接构建项目,而不必依赖系统中安装的 Gradle。
(四)使用 Gradle Wrapper 的好处
- 提高构建一致性:确保项目在所有开发环境和 CI/CD 环境中都使用相同版本的 Gradle,减少因版本差异导致的错误。
- 便于新开发者上手:新加入的开发者无需额外安装 Gradle,直接使用项目中的 Wrapper 即可参与开发。
- 更便捷的版本管理:项目升级到新 Gradle 版本时,只需更新
gradle-wrapper.properties
中的版本号即可,无需手动更新团队中每个人的 Gradle 环境。
二、在java-all-in-one项目中遇到的问题
在《一起学Java(1)-[起步篇]教你如何创建一个Gradle管理的Java开源项目(java-all-in-one)》中,我研究使用的项目就是通过Gradle Wrapper就行配置管理,当时生成的方式是通过IntelliJ内置的配置自动生成的。生成文件包括前文介绍的Gradle Wrapper所必须的gradlew.bat
、gradlew
、gradle\wrapper\gradle-wrapper.properties
以及gradle\wrapper\gradle-wrapper.jar
等必要文件。一直以来,我通过IntelliJ工具直接调用Gradle Task都正常执行,直到最近我在研究JUnit的使用的时候,想通过graldew test
命令直接执行,却一直报错。
1
2
3
gradlew.bat test
错误: 找不到或无法加载主类 test
原因: java.lang.ClassNotFoundException: test
并且,执行gradlew –version命令,显示的版本却是我环境Java的版本,而不是gradle的版本,这个现象让我觉得有些奇怪。我在网上进行了很多查询,大多讲的都是环境中缺失Gradle Wrapper运行必要的文件,但在我的项目中,文件都在。
百思不得其解,于是我查看了我环境中的gradlew.bat脚本内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
发现一个问题,脚本中执行Java程序的代码,只配置了执行参数和Classpath,但是没有指定gradle相关的运行程序,不太正常。
1
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" %*
为了印证猜测,我上网搜索了gradlew.bat脚本源码,上述代码应为
1
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
替换后,再次运行gradlew build命令验证,一切恢复正常了。如此看来,应该是IntelliJ初始化Gradle Wrapper项目时,相关文件内容生成除了点问题,具体原因未知。为了避免其他差异,我用命令
1
gradlew.bat wrapper
将我的项目重新初始化了一遍,gradlew.bat
、gradlew
、gradle\wrapper\gradle-wrapper.properties
以及gradle\wrapper\gradle-wrapper.jar
都重新生成了,仔细对比发现其实gradlew.bat
文件差异还是挺大的。
好在问题解决了,暂结束这个话题。
所有代码已上传至:https://github.com/lihongzheshuai/java-all-in-one