Spring Boot启动流程分析
引言
早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧。今天让我们从spring boot启动开始,深入了解一下spring boot的工作原理。
为什么用spring boot
在使用一个东西或者一个工具之前,我们总是会问自己,我为什么要用?用他能给我带来什么好处?
* 最大的好处就是spring boot遵从了java**约定大于配置**不用面对一大堆的配置文件,spring boot是根据你用的包来决定提供什么配置。
* 服务器以jar包的形式内嵌于项目中,对于微服务满天飞的情况,spring boot天生适合微服务架构,方便部署。
* 提供devtools从此改代码就需重启成为历史。
有优点就一定有缺点,缺点来源于优点优点来源于缺点(感觉在说哲学问题了哈哈哈)
* 正因为配置对开发者不透明,不看源码会不清楚spring boot如何进行诸如jdbc加载、事务管理等,出现错误也很难调错。
* 自动配置之后要自定义配置需编码javaconfig,需要了解这些配置类api。
* 版本迭代太快,新版本对老版本改动太多导致不兼容,比如1.3.5之前的springboottest和1.4.0之后的springboottest。
只有合适的架构才是最好的架构如果能接受spring boot这些缺点,spring boot确实是一个可以提高开发效率的不错的选择。
启动流程
扯了这么多,该上正题了,让我们来看看spring boot是怎样启动和启动做了哪些事情。
以下代码是spring boot项目标准的启动方式,使用注解@springbootapplication并且在main方法中调用springapplication的run方法,就可以完成。我们就从这个run方法开始看看spring boot的启动过程。
@springbootapplication public class application { public static void main(string[] args){ springapplication.run(application.class,args); } }
我们进入run方法,可以看到最终是调用了 new springapplication(sources).run(args);new springapplication(sources).run(args); 这个方法,可以看到,springboot的启动可以分为两个部分,第一部分:springapplication的实例化;第二部分:调用该实例运行run方法。我们先来看看这个springapplication的实例化过程。
private void initialize(object[] sources) { if (sources != null && sources.length > 0) { this.sources.addall(arrays.aslist(sources)); } //判定是否为webenvironment this.webenvironment = deducewebenvironment(); //实例化并加载所有可以加载的applicationcontextinitializer setinitializers((collection) getspringfactoriesinstances( applicationcontextinitializer.class)); //实例化并加载所有可以加载的applicationlistener setlisteners((collection) getspringfactoriesinstances(applicationlistener.class)); this.mainapplicationclass = deducemainapplicationclass(); }
关键点在两个set方法上**
setinitializers((collection) getspringfactoriesinstances(
applicationcontextinitializer.class))** 和 **setlisteners((collection)
getspringfactoriesinstances(applicationlistener.class))**
这两个方法一毛一样,挑实例化applicationcontextinitializer讲一讲。
private <t> collection<? extends t> getspringfactoriesinstances(class<t> type, class<?>[] parametertypes, object... args) { //拿到类加载器 classloader classloader = thread.currentthread().getcontextclassloader(); // use names and ensure unique to protect against duplicates //使用loadfactorynames方法载入所有的applicationcontextinitializer的类全限定名 set<string> names = new linkedhashset<string>( springfactoriesloader.loadfactorynames(type, classloader)); //使用反射将所有的applicationcontextinitializer实例化 list<t> instances = createspringfactoriesinstances(type, parametertypes, classloader, args, names); //排序 annotationawareordercomparator.sort(instances); return instances; }
自动配置的关键就是这个 getspringfactoriesinstances方法,确切的说是这个方法里的loadfactorynames方法,浪我们看看这个loadfactorynames方法干了啥,咋就能实现自动配置。
public static list<string> loadfactorynames(class<?> factoryclass, classloader classloader) { string factoryclassname = factoryclass.getname(); try { enumeration<url> urls = classloader != null?classloader.getresources("meta-inf/spring.factories"):classloader.getsystemresources("meta-inf/spring.factories"); arraylist result = new arraylist(); while(urls.hasmoreelements()) { url url = (url)urls.nextelement(); properties properties = propertiesloaderutils.loadproperties(new urlresource(url)); string factoryclassnames = properties.getproperty(factoryclassname); result.addall(arrays.aslist(stringutils.commadelimitedlisttostringarray(factoryclassnames))); } return result; } catch (ioexception var8) { throw new illegalargumentexception("unable to load [" + factoryclass.getname() + "] factories from location [" + "meta-inf/spring.factories" + "]", var8); } }
可以看到这个方法就做了一件事,就是从meta-inf/spring.factories这个路径取出所有”url”来,我们可以去到这个路径下看看到底是些啥?
# initializers org.springframework.context.applicationcontextinitializer=\ org.springframework.boot.autoconfigure.logging.autoconfigurationreportlogginginitializer # application listeners org.springframework.context.applicationlistener=\ org.springframework.boot.autoconfigure.backgroundpreinitializer
这下大家都应该明白了,spring是通过将所有你加载的jar包中找到它需要的applicationcontextinitializer来进行动态的配置的,只要你有用到特定的maven包,初始化的时候会找这个包下的meta-inf/spring.factories的需要的类比如applicationcontextinitializer进行实例化bean,你就可以用了,不需要任何配置。
说到这已经将所有springapplication实例化说完了,只是在加载完applicationcontextinitializer和applicationlistener这之后还有一步,就是找到启动类所在的位置并且设入属性mainapplicationclass中。
接下来让我们回到new springapplication(sources).run(args)
方法来看看run方法是怎么run的。
public configurableapplicationcontext run(string... args) { //开启启动计时器,项目启动完会打印执行时间出来 stopwatch stopwatch = new stopwatch(); stopwatch.start(); configurableapplicationcontext context = null; failureanalyzers analyzers = null; configureheadlessproperty(); //获取springapplicationrunlistener并启动监听器 springapplicationrunlisteners listeners = getrunlisteners(args); listeners.starting(); try { applicationarguments applicationarguments = new defaultapplicationarguments( args); //环境变量的加载 configurableenvironment environment = prepareenvironment(listeners, applicationarguments); //启动后console的打印出来的一堆配置信息 banner printedbanner = printbanner(environment); //终极大boss->applicationcontext实例化 context = createapplicationcontext(); analyzers = new failureanalyzers(context); preparecontext(context, environment, listeners, applicationarguments, printedbanner); refreshcontext(context); afterrefresh(context, applicationarguments); listeners.finished(context, null); stopwatch.stop(); if (this.logstartupinfo) { new startupinfologger(this.mainapplicationclass) .logstarted(getapplicationlog(), stopwatch); } return context; } catch (throwable ex) { handlerunfailure(context, listeners, analyzers, ex); throw new illegalstateexception(ex); } }
从这个方法里面做的最关键的三件事情就是:
获取监听器并启动
加载环境变量,该环境变量包括system environment、classpath environment和用户自己加的application.properties
创建applicationcontext
private void preparecontext(configurableapplicationcontext context, configurableenvironment environment, springapplicationrunlisteners listeners, applicationarguments applicationarguments, banner printedbanner) { context.setenvironment(environment); postprocessapplicationcontext(context); applyinitializers(context); listeners.contextprepared(context); if (this.logstartupinfo) { logstartupinfo(context.getparent() == null); logstartupprofileinfo(context); } // add boot specific singleton beans context.getbeanfactory().registersingleton("springapplicationarguments", applicationarguments); if (printedbanner != null) { context.getbeanfactory().registersingleton("springbootbanner", printedbanner); } // load the sources set<object> sources = getsources(); assert.notempty(sources, "sources must not be empty"); load(context, sources.toarray(new object[sources.size()])); listeners.contextloaded(context); }
前两点没什么好说的,重点说说第三个,创建applicationcontext。创建applicationcontext又分为几部:实例化applicationcontext、preparecontext、refreshcontext。实例化applicationcontext会根据在之前我们说的webenvironment这个属性判断是使用webcontext类annotationconfigembeddedwebapplicationcontext还是普通context类annotationconfigapplicationcontext(在这里我们使用的是webcontext为例)然后通过反射进行实例化。applicationcontext实例化完了会进入preparecontext流程,这个preparecontext方法会加载之前准备好的environment进入context中,然后如果有beannamegenerator和resourceloader那么提前创建bean加载进applicationcontext,但是一般这两个都是空的,所以直接进入applyinitializers方法,将之前实例化的所有initializers进行初始化,所有的bean就是在这里进行bean的扫描和加载的因这次讲的是启动过程,所以不再细讲。最后把创建好的applicationcontext设置进入listener,preparecontext过程就结束了。最后是refreshcontext,这个就和spring的bean加载过程一致了,bean的注入、beanfactory、postprocessbeanfactory等等,详情可以去看看spring bean的生命周期。
总结
spring boot 初始化内容还是很多的,但是总结起来就四点:
* 创建springapplication实例,判定环境,是web环境还是普通环境。加载所有需要用到的initializers和listeners,这里使用约定大于配置的理念揭开了自动配置的面纱。
* 加载环境变量,环境变量包括system environment、classpath environment、application environment(也就是我们自定义的application.properties配置文件)
* 创建springapplicationrunlisteners
* 创建applicationcontext,设置装配context,在这里将所有的bean进行扫描最后在refreshcontext的时候进行加载、注入。最终将装配好的context作为属性设置进springapplicationrunlisteners,这就完成了一个spring boot项目的启动。
以上所述是小编给大家介绍的spring boot启动流程,希望对大家有所帮助
推荐阅读
-
Spring Boot启动流程分析
-
详解Spring Boot应用的启动和停止(start启动)
-
解决Idea启动Spring Boot很慢的问题
-
详解spring boot 以jar的方式启动常用shell脚本
-
如何在Spring Boot启动时运行定制的代码
-
Spring Boot启动过程完全解析(二)
-
Spring Boot 启动加载数据 CommandLineRunner的使用
-
Spring Boot启动过程(四)之Spring Boot内嵌Tomcat启动
-
Spring Boot启动过程(六)之内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动教程详解
-
Spring Boot启动过程全面解析(三)