出个系列总结一下SpringBoot,会偏底层一些。这篇主要介绍SpringBoot的大致启动过程,细节会在稍后文章中讲解。
使用SpringBoot会让人感到清爽,没有那么多的配置文件。笔者曾参与过Spring和Struts2的项目,虽然时间不长,但仍感受到了配置文件的复杂,难以维护。后来使用SpringBoot做web应用,只需一个程序入口main方法,启动后便能使用,无形中它帮助我们配置了一切,如果想修改,这一切也可以在properties/yml中修改。从软件工程学的角度来看,Spring是非常成功的产品,SpringBoot则更贴近用户,“约定大于配置。并不是很神奇的不用配置,而是在启动时帮助我们配置好了,除了自动配置,我们还会看看他的IOC、AOP的实现,包括常用的web服务器也内嵌在里面,所以接下来几篇我们会一探究竟。
启动
1 2 3 4 5 6
| @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
|
这是SpringBoot的启动类入口,在resources目录下还会有配置文件,在启动类中我们要做的事情就两个:
- @SpringBootApplication
- 调用类SpringApplication的静态方法run,传入启动类的class以及main的args
所以,通过这个静态方法run,肯定会对启动类上的注解以及配置文件进行解析。跟进静态方法run,我们会发现他做了两件事
- 实例化SpringApplication
- 调用实例方法run
接下来我们先从实例化看起。
实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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(); }
|
以上就是实例化,实例化并不复杂,也只是做些简单的工作,着重说一下应用程序类型、初始化器和监听器,后面会再次用到。
应用程序类型
虽然平时用SpringBoot最多的是作web程序开发,但是他支持的类型不止web,Spring中有上下文,后续会根据应用的类型去创建对应的上下文,所以会在这里做判断。
如果是web类型的,SpringBoot会启动内嵌的web服务器,否则不会,而web服务器也分为响应的和非响应两种。如何判断呢?编译后会根据全类名判断是否有相应的class,如果要应用是web相关的,自然会引入相关的class。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
|
SPI
在说监听器和初始化器之前,我们先来说说SPI。跟进前两者的代码发现,他们的初始化逻辑是相同的。
1
| public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
|
SpringBoot会通过类加载器遍历上面的文件,文件属于配置文件,里面以key-value的形式放入了数据,例如监听器的入参是ApplicaitonListener
的class,根据这个会找出对应的其他全类名,取出后再通过反射去初始化这些类。然后装入对应的容器。
监听器
1 2 3 4 5 6 7 8 9 10 11
| org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
|
这里列出一部分的spring.facories中的listener,可以发现每个listener都有其对应的功能,与之相对应的是event,对于每个事件也有他对应的类。由于类数量多,且职责明确,他类中的内容是很少的,只是对传入的对象进行处理,比如ClearCachesApplicationListener
,清除缓存。
这些类只是监听,换句话说是需要被动触发,目前只是初始化阶段,等后续run的时候会初始化事件广播等其他对象,会与这些监听器一起配合使用。这些监听器被初始化完成后会存放在List容器中。
初始化器
初始化器听起来是初始化用的,初始化什么呢?我们先看文件下对应的初始化器(与监听器同理,他是凭借ApplicationContextInitializer
)找对应的全类名
1 2 3 4 5 6
| 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.ServerPortInfoApplicationContextInitializ
|
我们知道context是Spring的核心,不同类型的应用程序上下文都不一样,上下文 这个对象也会有中众多属性,这些初始化器就起到了初始化上下文的功能。例如地一个就是设置warnings的。只是目前是将这些类初始化,并将对象放入List容器。他们实现了相同的接口,后续也会向监听器一般被触发,然后遍历这些初始化器将传入的上下文初始化(对属性赋值)。
Run
实例化完成后就是调用实例方法run了。总共就实例化和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
| 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; }
|
这部分内容较多,我们先承接初始化将上下文的准备工作说一说,其余的在后面的文章说再提。
RunListeners
通过名字可以看出,SpringApplicaitonRunListeners
掌握着各种listener,但是不是那么的直接。
1 2 3 4 5
| private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
|
可以看到,与获取监听器、初始化器的方式如出一辙。这里获取到的类只有一个,EventPublishingRunListener
。也就是说SpringApplicationRunListeners
持有类EventPublishingRunListener
,run方法中的所有listeners的方法内部都是调用EventPublishingRunListenr
的,相当于他的代理类。后者的内部又是如何实现的,如何关联之前实例化的listeners呢?
在EventPublishingRunListener
的内部持有两个重要的对象,一个广播器SimpleApplicationEventMulticaster
,例如:
1 2 3 4
| @Override public void starting() { this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); }
|
开始监听,会创建一个相应的事件,并将其广播出去。注意到一个参数this.application
,这是持有的另一个重要的对象:SpringApplication
。前面提到,有对他进行实例化,实例化的时候会设置两个属性:初始化器和监听器。这里将SpringApplication
传进去,后续自然会通过get方法去获取到listeners,再触发对应event的listener。
createApplicationContext
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
| public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
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); }
|
之前有提到,初始化的时候设置了应用程序类型,在这里会根据这个类型去创建对应的上下文,同样是通过反射进行的。
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 30 31 32
| 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); }
|
准备工作也不少,listeners的响应一个比较好的阶段划分标志。可以看到分为了contextPrepared和contextLoaded。前者主要是对上下文的处理,后者是对上下文中的工厂属性进行处理,后续得用工厂去生产bean。
contextPrepared
这个阶段主要做两件事:后置处理,初始化。我们一件一件看。
postProcess
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) { context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { if (context instanceof GenericApplicationContext) { ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader()); } } if (this.addConversionService) { context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); } }
|
这里主要为上下文装配工厂的beanNameGenerator
、资源加载器和类加载器。
applyInitializers
1 2 3 4 5 6 7 8
| protected void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } }
|
之前提到的初始化器,在这里会对刚生成的上下文进行初始化,配置相关属性。
contextLoaded
这里主要是创建BeanDefinitionLoader
,用来后续从xml、javaConfig中加载bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load(); }
|
总结
我们启动SpringBoot虽然很简单,但实际上内部做了非常多的事情,正是这些“约定”简化了我们的使用。核心是围绕着上下文来做的:
- 根据应用程序的类型创建对应的上下文
- 初始化器配置上下文
- 监听机制响应对应的事件