最近在渐渐学习spring的源码,卡在源码编译好多天,中间走了不少弯路。相信很多小白跟小编一样,在spring源码编译上出现了很多问题网上也找不到什么有效的解决方法,一方面对Gradle等一些基础技能掌握没有很深入,一方面也对Idea这个IDE掌握不够,如果你现在用的是Idea2018版本+Spring5.0+Gradle4.9,那么很可能会出现一些问题(这些问题在文章结尾会总结一下),如果短时间内没有找到解决方案,那么建议你考虑用Idea2019.2.4+Spring5.0.1.x+Gradle5.6.3+JDK11.0.5的组合,这个组合经过了测试发现是可以正常编译出来的,下面就开始进入正题。
(一)准备所需要的工具:Idea2019.2.4+Spring5.0.1.x+Gradle5.6.3+JDK11.0.5_win64
Idea2019.2.4:可以在idea官网下载,网址在https://www.oracle.com/java/technologies/oracle-java-archive-downloads.html,
Spring5.0.1.x:spring源码可以在spring官网的github下载,https://github.com/spring-projects/spring-framework/tree/5.1.x,
下载Zip包到本地;
Gradle5.6.3:可到官网下载,https://gradle.org/releases/找到对应版本,选择binary-only版本即可;
JDK11.0.5:可到Oracle官网下载,但下载速度很慢,建议到CSDN下载会比较快。
(二)解压工程,配置环境变量:
1.运行JDK11.0.5.exe,配置java环境变量,配置好后控制台java -version会输出jdk11版本,当然也可以不修改环境变量,在idea中手动引用jdk11的地址也可以,地址为:C:\Program Files\Java\jdk-11.0.5;
2.在想要解压的盘符下解压Gradle压缩包,我这里是放在D盘下,Gradle的根目录在D:\gradle-5.6.3,然后在环境变量添加两个变量:GRADLE_HOME=D:\gradle-5.6.3,GRADLE_USER_HOME=D:\gradle-5.6.3\repogradle,其中GRADLE_USER_HOME的地址可以随便放,为了管理方便放在了D:\gradle-5.6.3\repogradle文件夹下,配置好后cmd执行以下gradle -version,有下面信息则配置完成:
------------------------------------------------------------
Gradle 5.6.3
------------------------------------------------------------
Build time: 2019-10-18 00:28:36 UTC
Revision: bd168bbf5d152c479186a897f2cea494b7875d13
Kotlin: 1.3.41
Groovy: 2.5.4
Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM: 11.0.5 (Oracle Corporation 11.0.5+10-LTS)
OS: Windows 7 6.1 amd64
3.安装idea2019.2.4.exe,双击安装在自己想要的文件夹下,安装有几个地方要留意下:
修改自己的安装路径:
选择插件,我这里选的是64位,同时所有的插件都勾上,因为spring用了groovy和kotlin插件,所以注意Create Associations这里最好全部勾上,并且Update context menu也勾上,这两个地方我是都勾了。
接下来一路next,
finish后,在弹出的对话框按默认往下next
直到下面这里:
到这里后先不急着导入项目,要先进行全局变量配置,比如Gradle配置,以及使用JDK配置,点击标记出的选项,选择Settings,会弹出设置页面,找到Gradle,一开始只会有配置gradle根地址的配置而已,先配置其他的东西在工程导入后再配置,如下:
设置好之后,保存这个设置关闭,就可以开始导入spring5的源码了,关闭这个设置后回到这个界面:
解压spring5的源码到你想要解压的目录下,在上面界面选择Import Project,选择spring5源码路径,开始导入,导入的过程需要下载jar编译依赖等,需要较长时间等待。可以将导入窗口点击background后台运行,在这期间确认这些配置是否正确,点击file-->settings,打开这个settings界面,找到gradle这时就有多个配置路径和jdk版本选择了,确认gradle地址和jdk版本是否没问题。
确认之后还要确认一个地方的配置,Apply后关闭Settings界面,打开file-->Project Structure,确认下面两个地方配置:
确认无误后Apply并关闭,回到导入Idea主界面,等待导入结束,如果网络正常情况下,一般可以顺利完成gradle引入和jar下载,当Idea下方的Build窗体内看到Build Success字眼,以及右边Gradle可以出现所有的工程结构时,说明基本上工程导入完成了。
接下来就是编译各个model包,在编译之前,首先要编译spring-core基本包,在右边Gradle中找到spring-code-->Tasks-->other-->compileTestJava,双击执行自动执行这些Test的Java类去构建这个spring-core工程,实际上所有的的包,如spring-beans,spring-oxm,spring-context等,都可以通过选中包-->鼠标右键Run tests in 'spring spring-xxx'的方式进行编译,编译成功的效果是,对应包下面会生成红色标记的build目录,如spring-core编译后:
由于我这里是学习spring IOC依赖注入的原理,IOC只需要依赖一个spring-context就足够了,为了后面学习方便,可以将
spring-beans,spring-oxm,spring-context这几个通过上面说的方法,选中包,鼠标右键run tests xxxx的方式进行编译,编译后这些包下都会有红色的build目录,目前没有发现这几个包在手动编译过程中出现什么问题。
这几个build之后,基本上spring源码就算编译完成了,可以自己新建一个model测试一下ioc功能,也是用这个自己新建的model在spring-context包中打断点,看spring ioc的底层运行原理,这些都是后面学习的内容了,首先还是新建一个model,点击file-->new-->Model,注意这里要选择gradle和只选择java,注意jdk版本选择:
然后next,可以随意填写这个model的名称,我这里填的是spring-myspring-test,填写好之后next直到窗口关闭,在这个spring5工程中就会看到这个spring-myspring-test的model:
然后添加对应的package和class,这里是为了学习ioc,所以建了一个TestIoc.java,主要提供main方法,新建一个AnnotationConfigApplicationContext,也就是我们熟悉的spring容器(spring容器和ioc完全不一样概念,不能混淆),采用目前spring推荐的javaConfig方式,引入Config.class,这个class声明为配置类并且只开启包扫描功能(扫描包是com.springtest.ioc包),这个com.springtest.ioc.dao包内创建一个普通的没有任何注解声明的UserDao.class并提供一个sayHello方法,最终往context容器传config类参数的方式,实现将这个com.springtest.ioc包内所有的普通类注入到ioc中,注入之后通过getBean方法获取对应的UesrDao,然后就可以调用对应的内部方法了。代码如下:
package com.springtest.ioc; import com.springtest.ioc.dao.UserDao; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * 测试IOC依赖注入,使用javaConfig风格 */ public class TestIoc { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); UserDao userDao = (UserDao) context.getBean("userDao"); userDao.sayHello(); } }
package com.springtest.ioc; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.springtest.ioc") /** * 声明为config配置类,该配置类进行包扫描功能 */ public class MyConfig { }
package com.springtest.ioc.dao; /** * 通过注解声明,先屏蔽注解后面再打开 */ //@Component public class UserDao { public void sayHello(){ System.out.println("UserDao hello"); }; }
代码编写好之后,还有一个Idea地方要修改一下,应该默认是用gradle的方式运行的,应该改为idea内部自带的方式运行,修改如下:file-->Settings-->Gradle:
接下来选中TestIoc.java,右键run Test Ioc.main()或者直接点击java文件中绿色箭头即可,运行过程中可能会出现如下报错:
看报错应该是spring-context中的类找不到,说明应该是这个spring-context的model没有编译好,将spring-context的build目录全部删了,在spring-context的test目录下重新run all tests编译出来,编译后重新dubug执行这个IocTest类,会运行到13行断点处:
在AnnotationConfigApplicationContext构造函数中打个断点,可以看到只有3行代码,这3行代码就是一个森罗万象的世界了,
这3行代码就是spring ioc值得学习的东西,这里暂时不做讨论先看结果,直接run到下一个断点处也就是IocTest的getBean方法,从前面和老师学习到的知识可知spring ioc存储各个BeanDefinition(对Bean的声明定义对象)实际上是在beanFactory工厂的beanDefinitionMap集合中(这个集合的对象会被new进ioc容器中),通过查看也就可以看到这个Map中有我们自己定义MyConfig,同时也有一些其他的BeanDefinition(有些如Processor这些都是spring自身需要使用的不需要通过注入,在spring产生时会马上创建出来并直接放入ioc的对象,因为这些对象在bean生命周期存在作用)。
可能眼尖的朋友很快发现了,为什么这里只能看到MyConfig,却看不到前面定义UserDao,这是因为MyConfig用了@Configuration等spring注解,这些注解就会被spring识别为可以注入到ioc的对象,但是UserDao并没有任何注解修饰所以在Map中并没有这个Bean对应的BeanDefinition,这也就解释了为什么加注解可以注入的原因了(将一个类变成Object注入ioc成为Bean的方式有很多种,如UserDao加注解只是其中一种,还可以通过context.register等方式注入),接下来在UserDao中加上@Component注解,重新debug运行,就可以可以看到这个BeanDefinition已经被放入到Map中如果不做Processor等人工干预就可以进一步被new到ioc中,getBean通过默认命名userDao名字可以得到这个userDao(默认命名以类名驼峰命名,可以修改命名改为ABC后通过geanBean("ABC")名称来获取userDao):
从上图就可以在Map中看到这两个BeanDefinition了,最终在控制台也会输出“UserDao hello”,全部源码编译就完成了,后续可以使用这个编译工程队源码进行debug学习。
在之前的学习中,了解到Spring是非常复杂的,IOC也是非常复杂的,一名小白刚刚开始想学得很好是非常非常难的,所以觉得可以尝试带着问题在后面的学习中带入学习会更有目标,比如:
1.一个class到object对象到bean,这个object和bean有什么关系,转化过程中有哪些重要的对象需要关注了解?
2.一个object从被初始化到注册成bean的生命周期过程是怎么样的,这个过程spring中有哪些组件分别负责了什么作用?
3.bean是怎么存储在哪里存储,bean怎么获取,获取到的是什么,为什么获取后可以直接调用方法等?
4.从源码中能否学习到有哪些注入方式,结合源码能否牵连到一些spring注解的原理作用(为什么要那样设计某个注解)?
5.是否可以关联到AOP的学习,IOC特性有什么的知识是和AOP特性有关联的?
............
路漫漫深深感到眼前犹如一片大海顿时不知所措,也希望这篇文档可以帮助到处于同样情况下的朋友,也欢迎留言交流遇到的问题。