天知道我今天下午经历了什么!从去年开始断断续续看源码,在 IDEA 中 Spring 源码只读不能写,所以每次都是将方法拷贝到 vscode 中。IDEA 中看代码逻辑,接着在 vscode 对应的方法上写注释。期初这种方式没觉得有什么不便,但是看得越来越多,代码的调用也越来越复杂,这种“复制”的方式就有问题了,代码层次没有,就是保存也很不方便。那最好的方式是直接在源码上写注释,用 git 工具做版本控制,So 就有了编译源码的想法。
理想很丰满,现实很骨感!一个掉头发的下午,还是在劳动节。搞定后觉得就是几个细节没有理清楚,写篇博客记录下,后面的人少采点坑。
1. 准备工作
我的配置是:Spring 5.2.14,Gradle 5.6.4,JDK 8,IDEA 2021。
默认 JDK 8 已经安装了。
1.1 下载 Spring 5.2.14 源码
git clone https://github.com/spring-projects/spring-framework.git
cd spring-framework
git reset --hard 4ceacfa70c37596c46147a5c2b56f07e470643d8
1.2 下载 Gradle 5.6.4
这个不是手动下载的,在命令行工具中执行编译命令时,会主动去本地目录 /.gradle/wrapper/dists/
找有没有 5.6.4 这个版本,如果没有就下载。这里怎么确定要使用 5.6.4 版本呢?
cat spring-framework/gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
gradle-wrapper.properties
文件中写了编译源码要求的 gradle 版本。
2. 修改几个配置
这里修改配置只是为了让编译能快一些,因为编译时会下载依赖,墙的缘故,将依赖源改成国内阿里源会快很多。
build.gradle
repositories {
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter'}
mavenCentral()
maven { url "https://repo.spring.io/libs-spring-framework-build" }
}
grade.properties
# 这个版本指的是 spring 源码的版本,不是 gradle 的,别瞎改。
version=5.2.14.RELEASE
org.gradle.jvmargs=-Xmx2048M
## 开启 Gradle 缓存
org.gradle.caching=true
## 开启并行编译
org.gradle.parallel=true
## 启用新的孵化模式
org.gradle.configureondemand=true
## 开启守护进程 通过开启守护进程,下一次构建的时候,将会连接这个守护进程进行构建,而不是重新fork一个gradle构建进程
org.gradle.daemon=true
settings.gradle
pluginManagement {
repositories {
// 加这个
maven { url "https://maven.aliyun.com/repository/public" }
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release' }
}
}
2. 编译
源码根目录下的 import-into-idea.md
文件中已经写了。
1. Precompile `spring-oxm` with `./gradlew :spring-oxm:compileTestJava`
2. Import into IntelliJ (File -> New -> Project from Existing Sources -> Navigate to directory -> Select build.gradle)
3. When prompted exclude the `spring-aspects` module (or after the import via File-> Project Structure -> Modules)
4. Code away
# -------------
1. 在源码根目录下执行命令:./gradlew :spring-oxm:compileTestJava。
2. 按照箭头的顺序,一直定位到源码的 build.gradle 文件打开。
3. 源码里没有 `spring-aspects` module 模块。
4. 使用代码。
在源码根目录下执行 ./gradlew :spring-oxm:compileTestJava
执行编译。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LQOb9Hep-1620054758234)(https://raw.githubusercontent.com/diego1109/pictures/main/20210503214830.png)]
本地没有,先下载 Gradle 5.6.4,再编译源码。这里是可以直接编译成功的,有些博客中写着导入到 IDEA 中再编译,我觉得那样就更麻烦,因为还得自己配使用那个 gradle,好些时候是导入了代码,忘了该配置,出现了不兼容还不知道问题在哪。(这个后面再说)
接下来就是打开 IDEA 导入 build.gradle
文件了,导入后等着,不需要任何手动配置 IDEA 自己执行 sync
,完毕后就能直接使用了。
3. 测试
IDEA 说 “SUCCESSFUL” ,我们还得自己测试一下到底能不能用。
3.1 创建一个新的 Module
创建完成后的测试项目目录:
# tree test-spring-source -L 3
test-spring-source
├── build.gradle
└── src
├── main
│ ├── java
│ └── resources
├── test
│ ├── java
│ └── resources
└── testFixtures
├── java
└── resources
3.2 写测试逻辑
修改 build.gradle
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
// 添加依赖
implementation(project(":spring-context"))
}
Main.java
package com;
import com.config.AppConfig;
import com.domain.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args){
// 创建 context ,并向其注册一个 User bean。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 将 User bean 拿出来。
User user = context.getBean(User.class);
// 查看内容。
System.out.println("name: "+ user.getName());
System.out.println("age: "+ user.getAge());
}
}
AppConfig.java
package com.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.domain")
public class AppConfig {
}
User.java
package com.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("diego")
private String name;
@Value("100")
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
最终目录结构:
test-spring-source
├── build.gradle
└── src
├── main
│ ├── java
│ │ └── com
│ │ ├── Main.java
│ │ ├── config
│ │ │ └── AppConfig.java
│ │ └── domain
│ │ └── User.java
│ └── resources
├── test
│ ├── java
│ └── resources
└── testFixtures
├── java
└── resources
运行结果:
> Task :test-spring-source-code:Main.main()
name: diego
age: 100
4. 踩的坑
**兼容问题:**Spring 版本,Gradle 版本,JDK 版本,编译失败都得归功于这三个不兼容。先确定 JDK 的版本,再去找 JDK 能支持的 spring 版本 和 gradle 版本。
spring:https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions。
gradle:https://docs.gradle.org/current/userguide/compatibility.html。
你编译时用的是哪个版本的 Gradle?
这先得回答一台电脑上怎么会出现多个 gradle 版本的?
这三条命令应该能说明问题了。首先电脑上肯定装了 gradle 这先是一个版本,此电脑上装的是 6.5.1。但是 gradle 是不断更新的工具,它会有版本迭代,而不同的 Spring 项目可能会用到不同版本的 gradle。这时候怎么办?升级或者降级 6.5.1,显然不靠谱。采用的方式是 当 spring 项目启动时,先检查其所要求的版本在本地有没有,如果没有,那先下载该版本,并保存到本地的 ~/.gradle/wrapper/dists
目录下,这就解释了为什么一台电脑上会出现多个 gradle 版本。
如果本地有多个版本,spring 项目编译时用哪个?
至于使用哪个,在 spring 项目的 gradle/wrapper/gradle-wrapper.properties
定义了,可以翻上去看看。
使用命令行工具,这里没什么大碍。
但如果使用 IDEA编译,坑货来了。使用 IDEA 编译失败,多半这里有鬼。原因是 IDEA 使用哪个版本编译,是要手动配置的。如果默认的正好符合,那完事大吉,如果不符合,开发者有忘了这里,那坑死人不偿命。
quan
圈住的地方是用来设置:本项目到底使用哪个 gradle。
gradle-wrapper.properties
: IDEA 推荐的方式,gradle 来源于项目的 gradle-wrapper.properties 中定义的版本。
wrapper task in Gradle build script
:task 中定义的,如果开发者喜欢控制 gradle 版本,可以使用这种方式。
Specified location
:开发者自己指定一个版本的 gradle 所在的目录。
gradle-setting更详细的信息。
最后想写的是:猜到坑了要淡定、要坚持。可以晚一点吃饭,可以晚一会上厕所,因为屁股离开凳子,等再坐上来就没了之前的感觉了。别拖。