概览SpringBoot的run方法主干逻辑
?
?点击关注公众号,”技术干货”及时达!???思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航??
?对于SpringBoot中run方法的分析网上已经有很多教程了,本文想试着从全局出发,重新梳理SpringBoot中run方法的整体脉络,使你不至于过分专注细节而迷失在run方法繁琐的调用逻辑中。
前言SpringBoot是基于Spring开发的一种轻量级的全新框架,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring 应用的整个搭建和开发过程。
开发者可以通过SpringBoot轻松地创建独立的,基于生产级别的基于Spring的应用程序。SpringBoot的出现极大的降低了开发应用的难度。除此之外,SpringBoot还具有如下特点:
可独立运行的Spring项目:Spring Boot可以以jar包的形式独立运行。
内嵌的Web容器:Spring Boot可以选择内嵌Tomcat、Jetty或者Undertow,无须以war包形式部署项目。
简化的Maven配置:Spring提供推荐的基础 POM 文件来简化Maven 配置。
自动配置: SpringBoot会根据项目依赖来自动配置Spring、SpringMVC等框架,极大地减少项目要使用的配置。
构建SpringBoot项目如果你曾经有过Spring、SpringMVC的开发经验,你一定会对其中繁琐的xml配置感到深恶痛绝,而SpringBoot的出现恰好可以解决这一痛点问题,SpringBoot可以让你更加快捷的搭建一个企业级应用。具体代码如下:
importorg.mybatis.spring.annotation.MapperScan;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
/**
*SpringBoot应用启动类
**/
@SpringBootApplication
publicclassLearnSpringBootApplication{
publicstaticvoidmain(String[]args){
//启动SpringBoot
SpringApplication.run(LearnSpringBootApplication.class,args);
}
}
通过上述代码不难发现,启动一个SpringBoot应用非常简单,我们只需如下几步:
提供一个SpringBoot的启动类LearnSpringBootApplication(注:启动类的名称可任意指定)在启动类上标注一个@SpringBootApplication注解编写一个main方法,调用SpringApplication中的run方法经过上述三步操作,就可以构建出一个简单的SpringBoot应用,随后运行main方法即可启动一个SpringBoot的应用。
可以注意到,在main方法中仅需调用一个run方法就能完成SpringBoot应用的启动。那这个run方法背后究竟又做了那些操作呢?接下来,我们便聚焦在SpringApplication的run方法中,看看run方法背后完成了那些操作。
run方法背后的逻辑进入到SpringApplication的run方法后,其最终入调用到如下的方法信息,相关代码如下所示:
?SpringApplication # run
?
publicstaticConfigurableApplicationContextrun(ClassprimarySource,String...args){
returnrun(newClass[]{primarySource},args);
}
publicstaticConfigurableApplicationContextrun(Class[]primarySources,String[]args){
returnnewSpringApplication(primarySources).run(args);
}
可以看到SpringApplication中对方法进行了重载,其最终会调用到run(Class[] primarySources, String[] args)。
此处run方法逻辑包含了两个操作:
构建一个SpringApplication对象执行SpringApplication对象的run方法信息接下来,我们将围绕上述的两个操作展开分析SpringBoot中run方法背后的逻辑。
创建SpringApplication对象publicSpringApplication(Class...primarySources){
this(null,primarySources);
}
publicSpringApplication(ResourceLoaderresourceLoader,Class...primarySources){
this.resourceLoader=resourceLoader;
Assert.notNull(primarySources,"PrimarySourcesmustnotbenull");
this.primarySources=newLinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType=WebApplicationType.deduceFromClasspath();
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass=deduceMainApplicationClass();
}
可以看到对于SpringApplication而言,其在构建时包含如下信息的构建:
ResourceLoader 指明资源加载器,这个暂时不用太过关注,应为其默认为null。webApplicationType 推断当前web应用类型,可通过一个deduceFromClasspath方法推断出的。随后设置了setInitializers、setListeners两个列表,分别是一堆Initializer和Listener,其都是通过getSpringFactoriesInstances方法获取。此外,还通过primarySources、mainApplicationClass记录了启动主要资源类,也就是之前传入的LearnSpringBootApplication.class。面对上述代码中诸多的陌生对象,你肯定什么都不清楚,不知道每个变量有什么用,是干嘛的,这没有关系。因为第一次分析你只要熟悉它的脉络就可以。知道在SpringApplication的构造方法中,会设置两个集合变量Initializer和Listener,了解这些就够了。
等之后你有时间,再逐个去了解每个变量或者组件的作用就可以了。事实上,SpringApplication的创建时的细节分析
你可以慢慢拆解上面的每一步,单独看看每一个组件大体是作什么的,这就属于细节研究了。
比如,你可以研究下ResourceLoader是什么?通过阅读它的类注释后可以发现,ResourceLoader 类负责使用ClassLoader加载ClassPath下的class和各种配置文件的。
对于webApplicationType类型如何被推断的?其本质就是根据几个静态变量定义的类全限定名称来进行判断的。具体而言,其会判断classPath下是否存在DispatcherServlet、DispatcherHandler等类来推断出类型。如果使用了web-starter则默认推断出为Servlet类型的应用。
至于primarySources、mainApplicationClass这个两个变量记录了启动类信息LearnSpringBootApplication.class, 其目的在于为了后续扫描包路径信息,完成自动配置等考虑的。
至于最后两个集合变量Initializer和Listener如何设置的,则比较考验阅读源码的能力了。其基本原理是通过ClassLoader扫描了classPath下所有META-INF/spring.factories这个目录中的文件,通过指定的factoryType,也就是接口名称,获取对应的所有实现类,并且实例化成对象,返回成一个list列表。比如, factoryType=ApplicationContextInitializer 就返回这个接口在META-INF/spring.factories定义的所有的实现类,并实例化为一个列表List ApplicationContextInitializer 。ApplicationListener同理。这里面其实有很多细节,大量使用了类加载器、缓存机制,反射机制等,有兴趣的话可以仔细研究下。
总结来看,上述构建SpringApplication的过程虽然会包括很多东西,但别慌,其概括成一句话就是:设定某些属性信息,然后通过classLoader获取classPath下指定位置某些接口的实现类和实例对象列表。
进一步, 构建SpringApplication时的相关逻辑如图所示:
熟悉了SpringApplication 的创建,接着我们该分析它的run(String ... args) 方法了。
SpringApplication Run的脉络run(String... args) 的逻辑如下:
publicConfigurableApplicationContextrun(String...args){
//.......省略其他无关代码
listeners.starting();
try{
//构建一个应用参数解析器
ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);
//加载系统的属性配置信息
ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,applicationArguments);
//用于控制是否忽略BeanInfo的配置
configureIgnoreBeanInfo(environment);
//打印banner信息
BannerprintedBanner=printBanner(environment);
//创建一个容器,类型为ConfigurableApplicationContext
context=createApplicationContext();
exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,
newClass[]{ConfigurableApplicationContext.class},context);
//容器准备工作(可暂时忽略)
prepareContext(context,environment,listeners,
//解析传入参数信息
applicationArguments,printedBanner);
//容器刷新(重点关注)
refreshContext(context);
afterRefresh(context,applicationArguments);
}
//.......省略其他无关代码
returncontext;
}
我们重点分析其中的refresh方法信息,对于refresh而言,其调用逻辑如下所示:
(注:如果你熟悉Spring中容器刷新的操作流程,相信你一定会秒懂图中蓝色方框内逻辑)
进一步,上面代码虽然看着复杂, 但本质主要就是执行了一堆方法。从方法名字看出,都是围绕Context、Environment这些术语。也就是围绕容器和配置文件组织的逻辑。另外,SpringBoot整个run方法中有几个很关键扩展点,设计SpringApplicationRunListeners、Runners等扩展入口。此外容器创建、刷新等也要各自的扩展点,对容器的增强扩展,如beanFactoryPostProcessor,对Bean的增加扩展,如beanPostProcessor。
(注:原图地址:https://baijiahao.baidu.com/s?id=1713152880461432834)
最后通过一张图来概括上面run方法脉络,其中:
黑色部分直观的反映了扩展逻辑相关逻辑白色部分是run方法每个方法的字面理解,只是每一步有很多扩展点和做的事情比较多,让你感觉会有点云里雾里的。蓝色部分,概括了核心逻辑。也就是SpringBoot启动,说白了我们核心就是要找到这三点:「自动装配配置、Spring容器的创建、web容器启动。」总结如果你第一次接触SpringBoot源码,可能会感到有些"不适"。这一定程度上是因为你过多的关注了源码细节,事实上,从细节中可以学习到知识,同时从主干脉络上也能学到知识。
而本文的主要作用是先为了梳理清楚一个run方法的脉络信息,让你熟悉run脉络的启动逻辑,让你清楚run的主干逻辑,之后如果你有兴趣要继续深入研究SpringBoot的启动逻辑只需沿着这一主干分析即可。
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线