前言
首先我们启动一个SpringBoot项目是怎么启动的?是依着Main方法中的SpringApplication.run(args);这个方法来进行启动的,很多人都对这个有点误解,以为是@SpringBootApplication赋予的超能力,所以就让Main方法成了SpringBoot启动类,我们下面就来刨析在我们的SpringBoot启动的过程中,经过了哪些步骤为什么在类上写个注解就能将其注入到Spring容器当中,以及说我们的自动配置是怎样实现的等等;
核心启动类
Main方法启动的时候调用了SpringApplication类的run方法,然而在这个Run方法执行的时候其实需要传递两个参数,第一个参数是当前的核心启动类的class,第二个是在调用Main方法时有可能是传递了一些参数的,所以也一并传过来;
在SpringApplication调用的这个两参run静态方法中,又进行调用了另一个重载run方法,然而在这个方法中,就略有些不同了,在这个方法中主要是做了两件事,第一是实例化当前的SpringApplication并且调用了他的有参构造,将我们的核心启动类传进去了;第二是调用了重载的run方法并且返回(我们项目中的所有需要注入到IOC容器的类、自动配置都是在这个方法中被调用执行的);
构造方法执行过程刨析
在进行调用单参数构造方法后,在方法中又进行调用重载的里两参数构造方法,并且将resourceLoader(资源加载器)设置为空;
进入这个两参数构造方法之后就能够看到SpringBoot在初始化SpringApplication所做的一些事情;
分为七步:
-
第一就是设置资源加载器;
-
第二断言加载资源(传进来的核心启动类)不能为空;
-
第三将核心启动类用List进行装载;
-
第四推断应用类型,推断应用类型之后,后面会根据初始化对应的环境,常用的环境一般都是Servlet环境,当然也有响应式,但是用的较少;
总共有三种类型的应用如图所示:NONE、SERVLET、REACTIVE;其中我们常用的是SERVLET;
-
第五初始化classpath下MATE-INF/spring.factories中已经配置的ApplicationContextInitalizer上下文监听器;
-
第一步是拿到类的加载器;
-
第二步是通过指定的类加载器,调用SpringFactoriesLoader.loadFactoryNames方法从所有的MATE-INF/spring.factories文件获取为type.class这个全限定名下的Value,并且将它转为Set集合装起来(去重);
-
创建Spring工厂实例,其实也就是说,将上方所获取到的所有需要的一些监听器全限定名,以反射的形式进行生成实例对象;
)] -
对Spring工厂中的一些实例对象进行排序(并且判断如果Order注解则优先级高些);
-
-
第六初始化classpath下MATE-INF/spring.factories中中所有已经配置ApplicationListener监听器;
以上方第五步相同的方式进行获取;
-
第七根据调用栈推断出来Main方法的类名;
run方法执行过程刨析
因为太长了,图片不清晰,直接上代码!!!
/**
*
* 【这个方法主要是做两件事:
* 1:加载ConfigurableApplicationContext上下文环境;
* 2:完成一些Bean对象的创建
* 】
*
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
* 运行Spring应用,并刷新一个新的 ApplicationContext(Spring的上下文(说白了就是各项配置))
* ConfigurableApplicationContext 是 ApplicationContext 接口的子接口,在 ApplicationContext基础上
* 增加了配置上下文的工具。ConfigurableApplicationContext是容器的高级接口
*/
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间(计时器)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//ConfigurableApplication Sping 的上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//1.获取并且启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//对SpringApplicationRunListeners中所有所包含的监听器进行启动
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2.构造应用上下文环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印Banner 也就是启动的时候打印的那个Spring
Banner printedBanner = printBanner(environment);
//3.使用多态的形势来初始化上下文,并将初始化完的上下文赋予变量context
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class 用来支持报告关于启动的错误
//调用getSpringFactoriesInstances方法来找到键名为SpringBootExceptionReporter全路径名的Value,并且进行实例化
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//4.刷新应用上下文前的准备阶段
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//5.刷新应用上下文
refreshContext(context);
//6.刷新应用上下文后的扩展接口
afterRefresh(context, applicationArguments);
//时间记录停止
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
在Run方法中主要的几步是:
-
获取并且启动监听器;
-
构建应用上下文环境;
构建上下文环境将环境都统一起来,例如像本机的环境,自定义的配置环境这些;
-
创建并且配置相应的环境;
-
根据用户配置,配置Environment系统环境,例如机器的名称等等;
-
启动相应的监听器,在启动的所有监听器中有一个非常重要 ConfigFileApplicationListener 这个监听器就是加载项目配置文件的监听器;
-
-
使用多态的形式来初始化上下文,并将初始化完的上下文赋予变量Context;
-
刷新应用上下文前的准备阶段(重点);
-
设置容器环境;
-
执行容器后置处理器;
-
将之前构造方法实例化的监听器进行启动;
-
向各个监听器发送容器已准备好的事件,告诉所有监听器有需要执行的方法即可执行;
-
将Main函数的args参数封装成单例Bean,注册进IOC容器
-
加载核心启动类,将核心启动类注入到IOC容器当中去,sources.toArray(newObject[0]),其实取出来的就是核心启动类
-
加载各种读取器,如注解、XML、将这些读取器准备好;方法中New了一个BeanDefintitionLoader()
-
再次调用重载的无参load方法;
-
在这个方法中判断了核心启动类上是否存在@Component注解,为什么能够进来,因为 核心启动类中的注解关系是这样的 @SpringBootApplication -> @SpringBootConfiguration -> @Configuration -> @Component,是这种组合的关系所以判断成立;
BeanDefinitionMap存储内部细节,其实这个就是IOC容器存储原理;位置是在DefaultListableBeanFactory类中的registerBeanDefinition方法
-
-
-
在最后发布容器已经加载事件提醒其他的监听器;
-
-
刷新上下文(重点);
在这里首先是断言了是否实例化了参数,然后调用了AbstractApplicationContext类中的refresh方法
别跟错了,这个是COnfigurationClassPostProcessor类中的postProcessBeanDefinitionRegistry方法
在这里我们使用DEBUG模式可以发现拿到所有Bean的候选名称中是包含imApplication的这也就说明核心启动类已经是被注册进IOC容器中的了;
中间是些排序没必要多看,直接看下面这个parse方法;
接下来调用了parse方法,parse方法主要目的是对核心启动类进行了解析;
在parse方法当中调用了processConfigurationClass;
绕了一大圈,重点来了!!!
调用了ComponentScanAnnotationParser类中的parse方法,这个parse方法中就是对注解的一些属性进行解析(图片是在方法后半段);
最终方法调用了一个doScan方法,在这个方法中获取当前项目中所有需要进行自动配置的类,并且将这些类都封装成BeanDefinitionHolder类型并且注册到IOC容器当中去;
这里的逻辑都差不多了,我们退回到ConfigurationClassParser类中的doProcessConfigurationClass方法中;
我们要进入的是getImports方法别搞错了;
在这里调用了collectImports方法;这个方法是获取所有使用了Import导入的一些类,然后将这些类进行加载;譬如:SpringBoot自动配置的原理AutoConfigurationImportSelector.class就是这样导入进来的;
在这里使用了一个递归算法,通过获取某个类或注解下的所有注解,利用类或注解下某项注解的全限定名,来进行与@Import注解全限定名匹配,如果成功则将该注解添加到imports类中,在这些类中,其中核心启动类包含两个这样的@Import注解,自动配置AutoConfigurationImportSelector.class也是这样导入进来的;
我们现在回到ConfigurationClassParser的parse方法中,看看process方法执行了什么;
看到这个getImports方法想必看过SpringBoot自动配置的朋友们应该很熟悉了吧,这个就是自动配置原理的入口;
在这个getImports方法中他又调用了process方法;
在这个process方法中主要做了几件事:
-
调用了getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
-
再次封装了自动配置类的autoConfigurationEntry对象将其组装进autoConfigurationEntries集合中
-
遍历刚获取到的自动配置类,将这些符合条件的自动配置类作为值放入到entries集合中;
这个方法已经解析完成,我们回到getImports方法中;
在这个方法中对autoConfigurationEntries这个集合进行排除以及对标有@Order注解的自动配置类进行排序
在这里可以看到所有的生效的自动配置类,但是有个问题是,他确实获取到了,在这过程中好像并没有看到它什么时候注册到IOC容器中去;
真正将自动配置类导入到IOC容器中的方法
这个第五步到这就结束了;
真正将自动配置类导入到IOC容器中的方法
这个第五步到这就结束了;
-
-
刷新应用上下文后的扩展接口;
这个其实是个空实现,它的作用其实就是做一个类似后置处理器,给程序员做扩展功能的,如果需要进行扩展就重写这个方法即可;