SpringBoot启动
Spring、SpringBoot启动属于是一个老生常谈的问题,以前整理过也比较碎片化。现重新进行一个整理。由于这个知识工作中用的不多,稍微深入一点太容易遗忘,作为一个长期更新的文档。有时间会更新一部分。
同时由于两者对于Spring的启动有很大的共通性,所有主要介绍SpringBoot的启动。毕竟现在新项目基本很少采用Spring Framework了。
这里不想贴太多的源码讲解。不然我会觉得显得文档太长了
前言
Spirng、SpringBoot的启动主要做了什么?
这个问题大了说,只考虑Spring的话(如果包含容器部分,比如tomcat,还可能设计到Servlet等知识),其实最主要就是加载Spring Bean。不管是xml、JavaConfig或者是直接申明的Bean,使用FactoryBean加载的Bean。所有的一切都是为了初始化Bean,初始化Bean的时候也就包括了我们启动需要的资源。
Servlet
SPI 机制
在讲解SpringBoot的启动时,首先要了解SPI模式使用。这里我就不详细讲解,主要是一种服务发现机制。可以看看dubbo的开发者文档,因为dubbo中的很多实现也是基于这个来做的。
只不过SpringBoot的SPI有自己的实现和使用规则,原理都是JAVA自带的SPI类似的。
SpringBoot是在META-INF的spring.factories
文件。
1 2 3 4 5 6 7
| org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
|
格式就是上诉的这种方式。ApplicationContextInitializer的6个实现类,注意最后不能加,\
。
SpringBoot在使用SPI的时候一般还会对其进行重排序,我们将接口实现Ordered
或者使用注解@Order
来决定排序。因为在使用的时候是按顺序调用的。
SpringApplication
1 2 3 4 5 6
| @SpringBootApplication public class WebBaseApplication { public static void main(String[] args) { SpringApplication.run(WebBaseApplication.class,args); } }
|
上面这段代码就是SpringBoot常用的启动类,按方法分解为两部分,构造函数和执行函数。 先看下构造函数
构造函数
1 2 3 4 5 6 7 8 9 10 11 12
| private List<ApplicationContextInitializer<?>> initializers; private List<ApplicationListener<?>> listeners; public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
|
这里就使用SPI来获取ApplicationContextInitializer
和ApplicationListener
的实现。以便后面可以使用。除了SPI其实你可以在启动类中用代码去addXXX自己的实现类。也是可以的,毕竟SpringBootApplication也是将它存在一个私有变量中。
run方法
接下来就是重点了,开始执行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 41 42 43 44 45 46
| public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); 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; }
|
首先这里又来一个新的监听器SpringApplicationRunListener
,也是通过SPI获取的。他其实是SpringBoot提供的一个接口,会在启动的调用该接口。我们可以通过该接口来在启动的各个阶段加入我们自己的逻辑。这个后面讲。
这里就没撒特别的了。
- 初始化environment
- 创建上下文
AnnotationConfigServletWebServerApplicationContext
- 准备上下文
- 刷新上下文
中间穿插了SpringApplicationRunListener的影子
prepareContext 准备上下文
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
| 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); } ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
|
准备上下文,就是在整个上下文(ApplicationContext)、配置文件(Enviroment)等资源文件。
这里有个applyInitializers(context)
就是执行最开始构造函数通过SPI拿到的ApplicationContextInitializer
实现类。主要就是在刷新上下文指定一些自定义的代码。比如对配置文件的处理。
刷新上下文
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
| @Override 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(); } } }
|
这里基本就是Bean的加载了。也就是IOC的主要部分。 找到一篇比较详细的文章推荐下! Spring-IOC
ApplicationListener
ApplicationListener是Spring Framework中的类。典型的观察者模式,我们可以在实现他对在Spring的启动,关闭等各个环节开启一个监听器。在设计模式中作为例子讲解一下
SpringApplicationRunListeners
SpringApplicationRunListeners是SpringBoot中的类。它相当于SpringBoot开放的一个钩子函数。我们实现它,SpringBoot在启动的各个环节都会去执行该接口。我们可以在SpringBoot启动中加入自己的逻辑
实际应用
其实看完上面的简单启动版本。其实有时候会在想实际开发中怎么用? 下面简单说明一下
@EnableAutoConfiguration、Condition接口
这两个其实才是实际框架集成到SpringBoot开发中用的最多的。
EnableAutoConfiguration具体实现可以看这篇文章,很不错的。EnableAutoConfiguration。
套用里面的一句话:@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!
这才是我们需要的重点将我们定义的Bean加载到Spring
那Condition是干什么的了?
1 2 3 4
| @FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
|
Condition是Spring Framework中一直都存在的一个接口(具体哪个版本开始就不清楚了)。SpringBoot将它发扬光大了对其做了很多实现
该接口和@Bean,@Component配合使用来决定Bean是否需要加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Configuration @ConditionalOnProperty(value = "com.myConfig.one",havingValue = "true") public class Config {
@Bean @ConditionalOnBean(OtherBean.class) public MyConfigBean myConfigBean() { return new MyConfigBean(); } }
|
上面就是一个简单的例子。该注解就可以灵活的控制我们的Bean的加载情况。
到此SpringBoot启动先做了一个简单的介绍整理。主要是因为在我查看了自己以前写的文档后。发现过于碎片化和无脑贴代码。觉得没什么意义,还不如很多网上已有的文档好。所以就直接剔除了。文档中也贴了两个写的可以的文档。后面有时间在整理这部分!