引言
SpringBoot 的出现,大大简化了 Spring 依赖配置,让我们可以快速搭起 Web 项目。本系列文章将带你从源码角度分析 SpringBoot 背后的原理。
run() 方法 我们知道,大部分语言都以 main 为起点,SpringBoot 也不例外,一个标准的 SpringBoot 启动类如下:
1 2 3 4 5 6 7 @SpringBootApplication public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
该类没有什么特殊的地方,只有一个 @SpringBootApplication
注解,外加一个 SpringApplication.run(DemoApplication.class, args);
方法。要想启动 SpringBoot 项目,只需要运行这个 main 方法即可,而该 main 方法做的唯一一件事就是调用 SpringApplication.run() 方法。所以我们把焦点放到这个方法,稍后我们在讨论 @SpringBootApplication
这个注解。
我们打开这个 run() 方法,可以看到该方法有一系列的重载方法,而我们入口方法签名如下:
1 2 3 4 5 6 7 8 9 public static ConfigurableApplicationContext run (Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); }
入参
返回值
ConfigurableApplicationContext 通过后缀 ApplicationContext
我们知道,它是一个 Bean 容器,而且是一个高级容器。具体作用,目前也是未知。
我们顺着这个 run 方法继续向下分析,发现它创建了 SpringApplication 对象:
1 2 3 public static ConfigurableApplicationContext run (Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
然后调用该对象的 run 方法。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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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; }
这个方法就是启动整个 SpringBoot 应用的核心方法了。内容不算太多,我把大体的流程用注释的方式进行说明。然后选择一个主线程,进行讲解。
启动监听器 在容器创建之前,会先进行监听器的获取与启动,我们来分析一下 SpringBoot 是如何拿到以及拿到哪些监听器的。
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)); }
从以上代码中我们看到,监听器的获取由 getSpringFactoriesInstances()
完成,(注意参数中的 this,也就是当前 SpringApplication 对象,它也就间接的传递给了监听器,用于构造实例 )。我们看下它的实现。
1 2 3 4 5 6 7 8 9 10 private <T> Collection<T> getSpringFactoriesInstances (Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
参数 type
通过调用我们知道其值为 SpringApplicationRunListener
,getSpringFactoriesInstances()
又将载入实现委托给了 SpringFactoriesLoader.loadFactoryNames()
。通过接下来调用的函数的名字我们也可以猜到,这一步是获取所有的目标类,然后通过 createSpringFactoriesInstances()
创建出具体的实例,排序后返回。
loadSpringFactories() 这里之所以把该方法单独拿出来是因为大部分的 Starter 组件中的 bean 都是由该方法发现的,所以很有必要说下它。
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 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null ) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]" , ex); } }
该方法其实没有什么特别的地方,基本上就是属性的获取之类的。唯一需要关注的就是 FACTORIES_RESOURCE_LOCATION
变量,而该变量的值为 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
。说到这,我想大家应该明白了,SpringBoot 就是到这个约定的路径下,到 spring.factories
文件中获取我们想要添加到系统容器中的 bean 信息。而 SpringBoot 的 starter 组件也是通过这个方式加载的。
SpringApplicationRunListeners SpringBoot 获取到的 SpringApplicationRunListener
类型的监听器其实就是在 spring-boot-x.x.x.RELEASE.jar
包下的 EventPublishingRunListener
监听器。然后将获取到的监听器保存到了 SpringApplicationRunListeners
中的 listener 集合中,我们随后看到 SpringBoot 调用了该监听器类的 starting() 方法,随之而来的就是触发 SpringApplicationRunListeners 中保存的所有监听器的 starting() 方法。我们这里只看 EventPublishingRunListener
的 starting() 方法实现。
1 2 3 public void starting () { this .initialMulticaster.multicastEvent(new ApplicationStartingEvent(this .application, this .args)); }
这里对事件进行了广播,广播对象为 SimpleApplicationEventMulticaster
(SpringBoot 对事件的广播都由该对象完成)。事件对象参数中的 this.application 就是之前传进来的 SpringApplication。
准备上下文 启动监听器之后,就开始了上下文的准备阶段,这里就会加载一批 bean 定义。
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 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); }
首先为当前容器对象设置环境变量对象,然后开始配置容器的后置处理器:
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 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()); } }
不熟悉没关系,这里我们只是留个印象,知道有这么个过程,至于它们的具体作用,在后续分析源码时在了解也不晚。再下来就是开始应用各个初始化器以及监听器的回调了。这里就不详细展开了,记住的只是每个阶段的生命周期,好在我们后续需要的时候能够随心所欲。
📚Tips
这里我们把 ApplicationContext 的实例称作高级容器,将 BeanFactory 称作低级容器。
load() 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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(); }
该 load 方法就是将入口类中涉及到的 bean 注册到 bean 容器中。其中 getBeanDefinitionRegistry 方法用于获取 bean 容器对象的 bean 定义注册器,因为 AnnotationConfigServletWebServerApplicationContext 默认实现了注册接口,所以这里返回的其实是容器对象本身。这里我们还需要注意的是 createBeanDefinitionLoader()
方法,我们看下它的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null" ); Assert.notEmpty(sources, "Sources must not be empty" ); this .sources = sources; this .annotatedReader = new AnnotatedBeanDefinitionReader(registry); this .xmlReader = new XmlBeanDefinitionReader(registry); if (isGroovyPresent()) { this .groovyReader = new GroovyBeanDefinitionReader(registry); } this .scanner = new ClassPathBeanDefinitionScanner(registry); this .scanner.addExcludeFilter(new ClassExcludeFilter(sources)); }
从代码中我们看到,这里创建了一个基于注解的读取器 — AnnotatedBeanDefinitionReader 以及一个类路径扫描器 — ClassPathBeanDefinitionScanner。这两个对象的入参都是 bean 容器。也就是说这两个方法会把获取到的 bean 信息存到 bean 容器中。
由于 load 调用路径过深,我这里就直接贴出最终的 load 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 private int load (Class<?> source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } if (isComponent(source)) { this .annotatedReader.register(source); return 1 ; } return 0 ; }
这里我们看到了一个”很熟悉”的方法 isComponent(source)
,通过它的名字我们可以猜出,它就是用来判断当前源是否包含 @Component
注解。如果是,开始调用基于注解的 bean 读取器注册相关信息。
1 2 3 4 5 6 7 8 9 10 11 12 public void register (Class<?>... componentClasses) { for (Class<?> componentClass : componentClasses) { registerBean(componentClass); } }
而 registerBean()
方法会将注册逻辑委托给 doRegisterBean()
方法。那么 bean 的定义信息最终会注册到什么地方呢?当然是 DefaultListableBeanFactory
中,对于 bean 的别名则是存到了 DefaultListableBeanFactory
的父类 SimpleAliasRegistry
中。
到目前为止,完成了 bean 定义的注册,这也是 prepareContext()
方法做的工作。接下来执行的就是上下文的刷新了,不过我们本文不讨论该方法,而是把它放到下一篇文章中,这么做一方面是因为该方法内容篇幅较大;另一方面是因为我们还有另一个知识点没有说明,那就是 @SpringBootApplication
注解。
@SpringBootApplication 注解 1 2 3 4 5 6 7 8 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
我们发现 @SpringBootApplication
注解中有一个比较熟悉的元注解 @ComponentScan
当然还有两个新注解 @SpringBootConfiguration
、@EnableAutoConfiguration
。我们看下比较陌生的两个注解。
@SpringBootConfiguration 1 2 3 4 5 6 7 8 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods () default true ; }
这个注解比较简单,就是组合了 @Configuration
注解,并且多了一个同 @Configuration
一样的属性 proxyBeanMethods()
该属性就是用来标记当前的 @Configuration
下带有 @Bean 方法的代理规则,以此来提升性能。
@EnableAutoConfiguration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; Class<?>[] exclude() default {}; String[] excludeName() default {};
该注解其实引入了两个配置类:AutoConfigurationImportSelector
以及 AutoConfigurationPackages.Registrar
。那么它们的作用是什么?不知道你有没有好奇过,SpringBoot 是怎么找到 starter 组件中的 bean,并且加载到当前容器中的?其实就是 AutoConfigurationImportSelector
中的逻辑实现的,它会去约定好的路径文件中读取 bean 信息,也就是 META-INF/spring.factories