前言 之前用SpringBoot的时候就很好奇,SpringBoot究竟是如何启动的呢?今天花了点时间稍微研究了一下,认真看下到底是如何实现的。
1 2 3 4 5 6 @SpringBootApplication public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class, args); } }
可以看到就是这个类的main方法启动了整个框架,主题就是一个SpringApplication类的一个静态的run方法,把当前类作为类对象进行了传入,并且还把参数也顺手传递了进去。就这么简单。所以重要的其实就是SpringApplication这个类。
除了这个类之外,还不要忽略了@SpringBootApplication这个注解,正是注解+代码结合在一起才让spring真正的启动了起来。
@SpringBootApplication 其实这个注解本质上就是@Configuration + @EnableAutoConfiguration + @ComponentScan,也就是我们理解了这三个注解基本就理解了启动过程的一半了。
@Configuration 这个注解其实就相当于声明这个类是一个配置类,相当于之前的spring的xml配置文件——即IoC容器的配置类。如果你理解spring的配置文件,这个也不难理解。
@EnableAutoConfiguration 简单来说,底层其实靠的就是@Import支持,而Import的本质就是引入配置文件。所以就是把所有符合自动配置的bean给加载到IoC容器中。
具体来说,其实就是把一个AutoConfigurationImportSelector放到了容器中,它的职能就是扫描所有的带有@Configuration的类,并且把它们加入到容器中。
@ComponentScan 在spring中,其实就是自动扫描并且把相对应的bean加入到容器中。
@Conditional 虽然这个注解并不算在启动三大将的注解中,但是通过它和它的兄弟们,我们可以按需加载一些配置,所以姑且拿出来讲一讲吧。
SpringApplication 首先站在全局的角度来高度浓缩一下启动流程:
收集好各种条件和回调的接口
创建并且准备好Environment
创建并且初始化ApplicationContext,加载配置等
refresh一下,ApplicationContext启动完成,结束!
然后再根据实际的代码来跟踪一下,发现其实调用的是静态方法是这样子的:
1 2 3 public static ConfigurableApplicationContext run (Object[] sources, String[] args) { return (new SpringApplication(sources)).run(args); }
首先使用了该类自己的构造方法创建了一个对象,并且调用了对象的另外一个run方法,所以如果硬要说的话,必须先构造出SpringApplication,然后在调用它的run方法。
构造方法 1 2 3 4 5 6 7 8 9 public SpringApplication (Object... sources) { this .bannerMode = Mode.CONSOLE; this .logStartupInfo = true ; this .addCommandLineProperties = true ; this .headless = true ; this .registerShutdownHook = true ; this .additionalProfiles = new HashSet(); this .initialize(sources); }
刨去一些初始化的东西,真正重要的其实是最后一句this.initialize(sources);,然后继续跟踪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void initialize (Object[] sources) { if (sources != null && sources.length > 0 ) { this .sources.addAll(Arrays.asList(sources)); } this .webEnvironment = this .deduceWebEnvironment(); this .setInitializers(this .getSpringFactoriesInstances(ApplicationContextInitializer.class)); this .setListeners(this .getSpringFactoriesInstances(ApplicationListener.class)); this .mainApplicationClass = this .deduceMainApplicationClass(); }
首先把重心放在if判断里面。可以看见是给类的成员变量source(一个hashset)增加了一个成员,即MainApplication.class。
然后判断一下当前是否是web环境,通过一个deduceWebEnvironment的方法来判断。这个方法判断逻辑也很简单,就是判断类加载器里面是否有特定的类。这个就不展开细讲了。
接下来就是设置好初始化器,并且做一些初始化的工作。
然后是一些应用程序事件监听器。
ApplicationContextInitializer 从这个名字里也不难得出结论,去spring.factories文件中读取指定的类,并且将它们进行实例初始化,并且设置成监听器,我跟踪了一下,有下面这些会被加载进来:
一共有六个,但是打开文件可以看到:
最后那两个怎么来的呢?
ApplicationListener 和上面的一样的,通过一样的方法,找到listener,并且进行实例初始化。
一共有10个,然后去spring.factories去看看:
和上面的一样,文件里只有9个,但是被实例化的却有10个。对比这两个我们不难发现,其实有一个autoconfigure包下面的也加了进来。这里我们先暂时不深究为什么会多出来。
这里要多说明一下,ApplicationListener就是用来监听ApplicationEvent的。
构造完成之后,执行之前 现在我们已经了解了,在构造函数中使用了initialize,而在这个方法中最主要的就是两件事:实例化一堆Initializer和一堆Listener。
Initializer比较好理解,就是完成初始化工作的。而Listener中有一个比较重要的,我特地把它的源代码拿出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface SpringApplicationRunListener { void starting () ; void environmentPrepared (ConfigurableEnvironment var1) ; void contextPrepared (ConfigurableApplicationContext var1) ; void contextLoaded (ConfigurableApplicationContext var1) ; void finished (ConfigurableApplicationContext var1, Throwable var2) ; }
其实看到这个接口的名字也知道了,就是用来监听SpringApplication的Run方法的。我目前只看到有一个EventPublishingRunListener的实现类。这个实现类用来在SpringBoot启动的不同阶段发布不同的事件,如果有哪个ApplicationListener对这些事件感兴趣,则可以接受并且处理。
更加具体的来说,它的实现类,会构造出一个一个的ApplicationEvent,并且把这个事件给广播 出去。而我们之前说那些ApplicationListener就会有相应。
OK,到了这一步,算是真真正正的把这个SpringApplication给构造出来了。
执行run方法 这一部分的逻辑见下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null ; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this .configureHeadlessProperty(); SpringApplicationRunListeners listeners = this .getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this .prepareEnvironment(listeners, applicationArguments); this .configureIgnoreBeanInfo(environment); Banner printedBanner = this .printBanner(environment); context = this .createApplicationContext(); exceptionReporters = this .getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); this .prepareContext(context, environment, listeners, applicationArguments, printedBanner); this .refreshContext(context); this .afterRefresh(context, applicationArguments); stopWatch.stop(); if (this .logStartupInfo) { (new StartupInfoLogger(this .mainApplicationClass)).logStarted(this .getApplicationLog(), stopWatch); } listeners.started(context); this .callRunners(context, applicationArguments); } catch (Throwable var10) { this .handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this .handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null ); throw new IllegalStateException(var9); } }
总得来说,确实比较长噢,那么稍微来整理一下。
new了一个stopWatch,并且启动了它。这个东西就是一个计时器,通过获取系统时间来实现的。
第7行,获取了SpringApplicationRunListeners(注意这个s),然后启动了它。实现的逻辑就是对于内部所有的SpringApplicationRunListener都调用其starting方法(当然现在来说只有一个)
第12行,构造了应用的参数,并且封装了一下。
第13行,准备好Springboot所需环境
第16行,在这一步,终于把ApplicationContext给构造了出来。
第19行执行了refresh操作。
第21行,停止了计时器。
第35行,返回了IoC容器。
最最重要的逻辑应该就是创建IoC容器的部分了吧,也就是createApplicationContext方法的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected ConfigurableApplicationContext createApplicationContext () { Class<?> contextClass = this .applicationContextClass; if (contextClass == null ) { try { switch (this .webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break ; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break ; default : contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass" , ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
可以看到很明显通过策略模式来选择创建了三种ApplicationContext,我们一般都会进入到SERVLET这个选项中,然后会为我们创建AnnotationConfigServletWebServerApplicationContext。
创建完成之后,马上执行了refresh操作,这个也是很值得回味的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }
这块的内容比较多,所以只是简单概括一下:首先可以看到是通过synchronized来进行同步的,然后就是BeanFactory的设置,BeanFactoryPostProcessor接口的执行、BeanPostProcessor接口的执行等等等。
然后是具体的逻辑部分。
####prepareRefresh
显然是在真正做refresh之前需要完成的事。
1 2 3 4 5 6 7 8 9 10 this .startupDate = System.currentTimeMillis();this .closed.set(false );this .active.set(true );initPropertySources(); getEnvironment().validateRequiredProperties();
prepareBeanFactory 这个方法主要做了这些事情:
告知内部的bean factory使用classloader,设置bean表达式的解析器,设置PropertyEditor(用来把2018/08/08转成date的东西)
为bean factory增加了一个ApplicationContextAwareProcessor,同时忽略了5个已经被自动注入的Aware接口。
实现一些资源加载、国际化等bean。
又加了一个ApplicationListenerDetector作为BeanPostProcessor,这个估计是新加的。
注册一些别的bean到容器中。
postProcessBeanFactory 这里根据不同的容器会进行不同的操作。
invokeBeanFactoryPostProcessors 以下暂时省略。
总结 SpringBoot的启动流程其实只要抓住两个流程,分别是SpringApplication对象的建立和run方法的执行。
在对象建立的时候:
判断是否是web环境
从spring.fatories中找到所有的Initializer,并且设置到一个List中(仅仅如此)
从spring.fatories中找到所有的listener,并且设置到另外一个List中(仅此而已)
在run方法执行的时候:
构造一个stopWatch用来记录时间。
找到SpringApplicationRunListener并且封装进SpringApplicationRunListeners
初始化容器并且完成容器的一些工作。