Spring源码研读之路(1)

Spring的诞生为那个“黑暗年代”(EJB1)带来了一丝曙光,那是一个 基于J2EE 规范统治的时代,框架中上层调用者“奴役”下层实现者,两者形成“强耦合”关系;各种第三方框架强迫开发者实现或者是继承指定接口,框架“侵入”应用之中;散布在应用中的各模块也因为“强耦合”关系,无法自由应用,那个“黑暗年代”沉重的研发模式和生态让开发者痛苦不堪。

一、前言

Spring框架可以说是日常开发过程中应用最多的框架了,但是我们对于框架应用也只是基于基本功能的使用,一旦遇到”循环依赖“,”Bean配置失效“或者是一些其他的问题就感到非常的头疼,因为我们不了解Spring的底层实现,正所谓说“知己知彼,百战不殆”,这样我们才能快速解决问题,同时还能定制化Spring应用,真正发挥Spring框架的高级功能。

在学习Spring框架中不仅只是学习框架更深层次的应用,也是为了学习Spring框架中的”设计模式“应用和框架中系统设计思想,同时学习”大佬“们是怎么写Java代码的。

在Spring源码研读过程中,需要进行大量的代码调试以及做笔记,因此第一件事情就是要看源码,不仅要能看,还要能模,所以我们需要把Spring源码编译一下。

二、工具,环境,Spring源码版本说明

1、Spring源码版本

Spring源码Github地址:https://github.com/spring-projects/spring-framework/tree/5.0.x

image

这边选择是”5.0.X“版本的Spring源码,还有”5.1.X“以及”5.2.X“的版本。同时这边采用下载”ZIP“代码包的方式进行编译,毕竟国内的网速。。。

这边先介绍要使用的Spring源码包,是因为Spring源码包中有说明要采用的”Gradle“版本,在路径“spring-framework-5.0.x\gradle\wrapper”下,

image

打开“gradle-wrapper.properties”是看到如下内容:

image

这边需要关注“GRADLE_USER_HOME”以及“gradle-4.4.1-bin.zip”,这一部分的内容在“Gradle环境”中详细说明。

2、环境

2.1、Java环境

这是最基本,这里关于Java环境不多介绍,关于Spring源码编译上,官方也没指定什么版本,所以只展示当前电脑(Win10系统)上Java环境

image

2.2、Gradle环境

(1)Gradle版本选择

在上述Spring5.0.X源码包中显示Gradle版本要求是“gradle-4.4.1-bin.zip”,不一定要按照上面说,也可以自己选择Gradle版本,但是不能选择太高的版本,之前试过“Gradle-6.4”版本过高导致编译异常,最终选择“gradle-4.9-all”版本

Gradle官网:https://gradle.org/(如果不方便,在文末为有感谢各位博主的连接,可以下载)

(2)Gradle配置

如果是开发安卓的小伙伴,对Gradle不陌生,但是我是作为Java后台开发,平常开发过程中还是使用Maven构建工程居多,对于Gradle也不是很熟悉,因此只是配置一下Gradle环境。

配置"GRADLE_HOME"环境变量

这一步和配置JAVA_HOME非常的类似,新建系统变量“GRADLE_HOME”写上,Gradle下载好之后并解压的目录

image
image
配置“PATH”环境变量

在系统环境变量“PATH”中编辑配置写上“%GRADLE_HOME%\bin”即可。

image
配置“GRADLE_USER_HOME”环境变量

这一步的配置是指定Gradle的“本地仓库”所在的位置,这边可以写成与Maven的“本地仓库”所在的路径一样。

image
配置“init.gradle”初始化文件

在Gradle压缩包解压之后,存在一个“init.d”文件夹,但是这个文件夹里面是空的,因此需要手动创建“init.gradle”文件,这一步是配置Gradle构建项目中Jar依赖的下载地址

image

创建好之后,配置文件内容如下:

image
allprojects{ 
  repositories { 
    def REPOSITORY_URL ='http://maven.aliyun.com/nexus/content/groups/public/' 
      all { 
            ArtifactRepository repo-> def url =repo.url.toString() 
            if ((repoinstanceof MavenArtifactRepository) &&(url.startsWith('https://repo1.maven.org/maven2')||url.startsWith('https://jcenter.bintray.com'))) { 
            project.logger.lifecycle'Repository ${repo.url} replaced by $REPOSITORY_URL .' remove repo 
      } 
} 
    maven { 
        url REPOSITORY_URL
       } 
    } 
  }

以上的内容均配置完成之后,可以查看最终结果


image.png

这边同样需要注意一下信息

Kotlin DSL:   0.18.4 
Kotlin:       1.2.41 <----当前Kotlin版本 
Groovy:       2.4.12 <----当前Groovy版本 
Ant:          Apache Ant(TM) version 1.9.11 compiled on March 23 2018 
JVM:          1.8.0_65 (Oracle Corporation 25.65-b01) <----当前JVM版本 
OS:           Windows 10 10.0 amd64

3、工具

当前电脑上用的是“IntelliJ IDEA 2019.3.3 ”的IDEA版本,不过Spring项目编译过程中对于IDEA的版本关系倒是不大,最关键的是各种版本需要对应上。


image.png

三、Spring源码配置

Spring源码在编译之前需要做一些基本配置,保证源码一致,这样能有效提高Spring源码编译成功率。

1、配置“gradle-wrapper.properties”

关于“gradle-wrapper.properties”是配置Spring编译过程中对于Gradle环境的要求,之前提到过,可以通过该配置文件知道Gradle版本,但是在实际编译过程中,会遇到如下情况

image.png

即使是你按照上述过程配置好Gradle环境,依然会出现,Downloading下载Gradle安装包
因此,按照网上参考的解决办法是,修改“gradle-wrapper.properties”中“distributionUrl”属性为采用的Gradle版本,同时直接把已经下载好的Gradle包拷到该文件夹下。
image.png

效果如下:
image.png

2、配置“docs.gradle”文件

该文件位于“spring-framework-5.0.x\gradle”下

image.png

这一步是去除编译过程中关于JavaDoc文档的构建,在Spring源码编译过程中,JavaDoc文档编译非常的消耗时间,甚至4-5小时编译不下来,可能和我电脑性能也有关系。
注释掉“dokka”以及“asciidoctor”内容
image.png

image.png

(PS:说明一下,这边主要是涉及到JavaDoc的编译,在实际过程中编译时间太久的话,会直接禁用Gradle编译文档的Task<任务> ,因此这一步可做可不做)

3、配置“gradle.properties”配置文件

该文件位于“spring-framework-5.0.x”,Spring源码包直接解压之后的根目录下


image.png

配置效果如下:


image.png

配置内容如下:
version=5.0.19.BUILD-SNAPSHOT 
## 开启缓存 
org.gradle.caching=true 
## 开启守护进程 
org.gradle.daemon=true 
## 设置此参数主要是编译下载包会占用大量的内存,可能会内存溢出 
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 
## 开启并行编译  
org.gradle.parallel=true 
## 启用新的孵化模式
org.gradle.configureondemand=true

4、配置“build.gradle”项目构建文件

该文件位于“spring-framework-5.0.x”,Spring源码包直接解压之后的根目录下


image.png

在这个构建的配置文件中,需要做两件事情
(1)配置远程仓库下载地址
配置效果如下:


image.png

配置内容如下:
maven { url "http://maven.aliyun.com/nexus/content/groups/public/"}

(2)核对当前版本是否保持一致

上述配置Gradle环境中,我们可以看到当前“Kotlin”以及“Groovy”版本,修改配置文件中相对应的版本

image.png

image.png

指定版本保持一致

四、Spring源码导入IDEA

在上述Spring源码配置完成之后,就可以把源码导入IDEA中

1、Spring源码导入IDEA中

在IDEA左上角上选择“File -> New -> Project from Existing Sources ”,然后再选择Spring源码目录下的“build.gradle”文件

image.png

image.png

导入项目之后,在IDEA右侧“Gradle”便会出现“spring”结果
image.png

导入项目之后,IDEA便会开始自动的下载依赖,不过一般来说第一次导入依赖多会失败,还需要配置一下IDEA相关内容

2、配置IDEA

1、项目Gradle配置

打开“File -> Setting

image.png

2、 配置“Kotlin Compiler"

打开“File -> Setting

image.png

3、配置项目JDK

打开”File -> Project Structure

image.png

4、刷新依赖
image.png

点击右侧打红框的小图标开始刷新依赖,这是一个比较漫长的过程。。。


image.png

这是表示依赖刷新成功.

五、编译Spring源码

当依赖刷新好了之后,便可以开始编译源码了,在Spring源码解压之后,可以发现Spring源码中自带了编译教程,一个是关于IDEA,还有一个就是关于Eclipse


image.png

我们只看关于IDEA的就好


image.png

1、编译”spring-core“以及”spring-oxm“模块

`spring-core` and `spring-oxm` should be pre-compiled due to repackaged dependencies. 
翻译大致如下: 
由于重新包装了依赖关系,应预先编译“ spring-core”和“ spring-oxm” 

按照提示我们应该先编译”spring-core“以及”spring-oxm“模块

在IDEA操作如下:

文中要求执行”./gradlew :spring-oxm:compileTestJava“其实就是对应如下操作


image.png

关于”spring-core“编译同样如此操作:


image.png

2、移除”spring-aspects“模块
`spring-aspects` does not compile due to references to aspect types unknown to IntelliJ IDEA. See https://youtrack.jetbrains.com/issue/IDEA-64446 for details. In the meantime, the 'spring-aspects' can be excluded from the project to avoid compilation errors. 
翻译大致如下: 
由于引用了未知的方面类型,因此无法编译“ spring-aspects” IntelliJ IDEA。 有关详细信息,请参见https://youtrack.jetbrains.com/issue/IDEA-64446。 同时, 可以从项目中排除“ spring-aspects”,以避免编译错误。

因此在编译过程中需要先移除”spring-aspects“模块


image.png

3、编译Spring源码。

上述过程中都操作好了之后,就可以开始编译Spring源码


image.png

编译Spring源码需要花费挺长的时间,而且编译过程中也会需要各种各样的问题,在编译过程中如遇到以下问题,可以参考一下解决办法。

4、编译过程常见问题

(1)No such property: values for class: org.gradle.api.internal.tasks.DefaultTaskDependency

详细报错信息如下:

* Where: Build file 'D:\git��Ŀ\springԴ��\spring-framework-5.0.x\spring-framework-5.0.x\spring-beans\spring-beans.gradle' line: 28   * 
What went wrong: A problem occurred evaluating project ':spring-beans'. 
> No such property: values for class: org.gradle.api.internal.tasks.DefaultTaskDependency   
Possible solutions: values

参考解决办法:


image.png

后三行替换内容如下:

def deps = compileGroovy.taskDependencies.immutableValues + compileGroovy.taskDependencies.mutableValues 
compileGroovy.dependsOn = deps - "compileJava" 
compileKotlin.dependsOn(compileGroovy) 
compileKotlin.classpath += files(compileGroovy.destinationDir)

(2)Execution failed for task ':spring-beans:javadoc'.

详细报错信息如下:

FAILURE: Build failed with an exception.   
* What went wrong: Execution failed for task ':spring-beans:javadoc'. 
> Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting): 'E:\myspring\spring-framework-4.3.10.RELEASE\spring-beans\build\tmp\javadoc\javadoc.options'

参考解决办法:


image.png

修改内容:

options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED options.author = true 
options.encoding = "UTF-8" 
options.header = project.name options.use = true 
options.links(project.ext.javadocLinks) 
options.addStringOption("Xdoclint:none", "-quiet")

(3)等待了很长的编译时间,还是编译未完成
问题描述:在编译过程中如果模块一直处在JavaDoc编译过程中,无法编译完成


image.png

点击此步骤可以查看完整的编译过程,


image.png

在编译过程中如果有发现类似“doc”,或者是类似“javaDoc”的字样,就是说明在编译JavaDoc文档,这是一个非常非常耗时的过程,因此可以禁止该Task<任务>的执行
解决办法如下:
image.png

image.png

把这些都禁止掉,能加快编译速度。

(4)编译过程中卡在“test”环节,或者是其他环节导致编译无法成功

在Gradle编译项目或者是Maven编译项目过程中,都会经历“Test”环节,因此编译过程可以跳过Test环节

解决办法如下:


image.png

输入以下Build命令:

gradle build -x testClasses -x test -x javadoc -x compileTestKotlin 也可以用于跳过测试,以及文档构建环节

效果如下图:


image.png

六、构建模块测试

最终Spring项目编译完成之后。并不代表,项目就能能正常使用的,因此还需要构建一个简单的模块进行测试。

1、创建一个模块
image.png

创建模块方式类似Maven创建模块一样。

2、在“build.gradle”引入相关的依赖

在一个模块下“build.gradle”就是类似Maven的pom文件


image.png

引入Spring相关依赖:

dependencies {
    compile(project(":spring-context"))
    compile(project(":spring-beans"))
    compile(project(":spring-core"))
    compile(project(":spring-aop"))
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
3、开始测试
image.png

创建一个Bean对象

public class User {
    private String name;

    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public User() {
    }

    @Override
    public String toString() {
        return "MyName:"+this.name+",MyAge"+this.age+" old";
    }
}

创建配置类

@Configuration
@ComponentScan
public class ApplyConfig {

    @Bean
    public User user(){

        User user = new User();
        user.setAge(18);
        user.setName("张三");

        return user;
    }

}

应用主类

public class MyMain {
    public static void main(String[] args) {
        System.out.println("Hello Spring");

        ApplicationContext ac =new AnnotationConfigApplicationContext(ApplyConfig.class);
        User user = (User) ac.getBean("user");
        System.out.println(user.toString());
    }
}

执行结果


image.png

到此Spring源码算是编译成功!

PS:关于项目控制台输出出现乱码,即使设置了“UTF-8”还是会出现乱码


image.png

image.png

添加如下内容:

-Dfile.encoding=UTF-8

添加完成之后,一定要记得重启IDEA!这样配置才能生效。

七、参考文献

在此次搭建Spring源码阅读环境中,也是遇到非常多的坑,在踩坑过程中参考了诸多博主的博客内容才得以解决问题,感谢这些博主所分享的内容。

  • Spring相关博客连接:

码之初 ttps://www.cnblogs.com/mazhichu/p/13163979.html

Dcwjh https://blog.csdn.net/Dcwjh/article/details/104471560

疯狂的暴走蜗牛 https://blog.csdn.net/u010936936/article/details/103404842

微瞰技术 https://blog.csdn.net/u011342403/article/details/104485619

  • Gradle相关博客连接:

visionarywind https://www.jianshu.com/p/ff5d9c33c108

刘亚芳 https://www.jianshu.com/p/d9329117aa2f (各Gradle版本下载)

你可能感兴趣的:(Spring源码研读之路(1))