1、SpringBoot maven 依賴版本
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>SpringBootStudy</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.14</version> </dependency> </dependencies> </project>
2、啓動代碼
package com.springboot.study; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Main.class); springApplication.run(args); } }
3、啓動流程分析
1、獲取Spring工廠實例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); 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(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
這個地方會掃描項目中 resource 目錄下的 META-INF/spring.factories 文件,默認如果不添加其他依賴,會掃描到如下項目:
- spring-boot-2.7.14.jar
- spring-boot-autoconfigure-2.7.14.jar
- spring-beans-5.3.29.jar
最終會掃描到如下對象:
2、run 方法分析
public ConfigurableApplicationContext run(String... args) { long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } listeners.started(context, timeTakenToStartup); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
1、首先是 createBootstrapContext 方法,該方法會調用第一個擴展點:BootstrapRegistryInitializer->initializer,但是項目中沒有該接口的實現類。
2、其次就是 listeners.starting 方法, 該方法中會調用第二個擴展點 SpringApplicationRunListener->starting,這個 SpringApplicationRunListener 項目中只存在一個實現類:EventPublishingRunListener,它會觸發所有的 ApplicationListener 監聽 ApplicationStartingEvent 的事件,後文就不特別聲明這個實現類了。
2.1、配置屬性(prepareEnvironment->configureEnvironment)
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); if (!CollectionUtils.isEmpty(this.defaultProperties)) { DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources); } if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite .addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
3、然後就是 prepareEnvironment 方法,該方法會調用第三個擴展點:SpringApplicationRunListener->environmentPrepared,它會觸發所有的 ApplicationListener 監聽 ApplicationEnvironmentPreparedEvent 的事件。
2.2、配置 Banner
private Banner getBanner(Environment environment) { Banners banners = new Banners(); banners.addIfNotNull(getImageBanner(environment)); banners.addIfNotNull(getTextBanner(environment)); if (banners.hasAtLeastOneBanner()) { return banners; } if (this.fallbackBanner != null) { return this.fallbackBanner; } return DEFAULT_BANNER; }
4、第四個擴展點在 prepareContext->applyInitializers 方法裏,ApplicationContextInitializer->initialize。
5、第五個擴展點在 prepareContext->listeners.contextPrepared 方法裏,SpringApplicationRunListeners->contextPrepared,它會觸發所有的 ApplicationListener 監聽 ApplicationContextInitializedEvent 的事件。
6、第六個擴展點在 prepareContext->bootstrapContext.close 方法裏,它會觸發所有的 ApplicationListener 監聽 BootstrapContextClosedEvent 的事件。
2.3、加載 Source(prepareContext)
public Set<Object> getAllSources() { Set<Object> allSources = new LinkedHashSet<>(); if (!CollectionUtils.isEmpty(this.primarySources)) { allSources.addAll(this.primarySources); } if (!CollectionUtils.isEmpty(this.sources)) { allSources.addAll(this.sources); } return Collections.unmodifiableSet(allSources); }
private void load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class<?>) { load((Class<?>) source); return; } if (source instanceof Resource) { load((Resource) source); return; } if (source instanceof Package) { load((Package) source); return; } if (source instanceof CharSequence) { load((CharSequence) source); return; } throw new IllegalArgumentException("Invalid source type " + source.getClass()); }
這個地方會從 SpringApplication 中的 primarySources、sources 加載源,然後註冊爲 Bean。
7、第七個擴展點在 prepareContext->listeners.contextLoaded 方法裏,SpringApplicationRunListeners->contextLoaded,會觸發 ApplicationListener 監聽 ApplicationPreparedEvent 的事件。
2.4、ServletWebServerApplicationContext->refresh
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); contextRefresh.end(); } } }
這裏我們直接看到 invokeBeanFactoryPostProcessors 方法,不過這個方法蠻長的,就先看一部分:
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; registryProcessor.postProcessBeanDefinitionRegistry(registry); registryProcessors.add(registryProcessor); } else { regularPostProcessors.add(postProcessor); } }
這個地方可以猜到這個 postProcessBeanDefinitionRegistry 也是一個擴展點,但是這個 beanFactoryPostProcessors 的值是從哪裏來的呢?
打斷點這裏是有三個值的:
這裏直接說答案,還記得這段代碼嗎:
context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
這裏獲取了 context 之後,就調用了 prepareContext 方法,prepareContext 方法裏曾提到過有第四、第五、第六、第七個擴展點。
舉個例子,就拿第四個擴展點說:
ConfigurationWarningsApplicationContextInitializer 實現了 ApplicationContextInitializer 接口,然後添加了一個值:
@Override public void initialize(ConfigurableApplicationContext context) { context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks())); }
相同的還有 SharedMetadataReaderFactoryContextInitializer,至於第三個值則是直接在 prepareContext 方法裏添加的。
8、第八個擴展點在 refresh->invokeBeanFactoryPostProcessors 方法裏,會調用 invokeBeanDefinitionRegistryPostProcessors。
緊接着,就是如下代碼:
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } }
我們看看這個 postProcessorNames 是怎麼來的,追蹤到 beanFactory.getBeanNamesForType 方法,進去可以看到 doGetBeanNamesForType 方法,總而言之,是從 beanDefinitionNames 和 manualSingletonNames 的值來的,那這些值又是怎麼來的呢,仍然是如下一段代碼:
context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
在創建 context 的時候,實際上是調用 AnnotationConfigApplicationContext 的構造方法,裏面會調用 registerAnnotationConfigProcessors 方法,這個方法會初始化 5 個 beanDefinitionName。
然後就是 prepareContext 方法裏,在第四個擴展點會初始化兩個 manualSingletonName,prepareContext 方法裏的 registerSingleton 方法也添加了兩個 manualSingletonName,並且後面的 load 方法也添加了一個 main 的 beanDefinitionName,在最後的第七個擴展點裏又添加了 3 個 manualSingletonName,總之在執行 refreshContext 之前,context 的 beanFactory 裏包含 6 個 beanDefinitionName 7 個 manualSingletonName,後面的就不分析了,畢竟能擴展的就在這裏。
9、第九個擴展點也在 refresh->invokeBeanFactoryPostProcessors 方法裏,會調用 invokeBeanFactoryPostProcessors。
不過有一點要注意的是,雖說第八個、第九個也算是擴展點,但其只有在第四到第七個擴展點裏面配置了才能進行擴展。
2.5、refresh->onRefresh
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
該方法會調用 createWebServer 創建一個 webserver。
2.6、refresh->finishBeanFactoryInitialization
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // Initialize conversion service for this context. if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } // Register a default embedded value resolver if no BeanFactoryPostProcessor // (such as a PropertySourcesPlaceholderConfigurer bean) registered any before: // at this point, primarily for resolution in annotation attribute values. if (!beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); } // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } // Stop using the temporary ClassLoader for type matching. beanFactory.setTempClassLoader(null); // Allow for caching all bean definition metadata, not expecting further changes. beanFactory.freezeConfiguration(); // Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons(); }
這個方法會調用 beanFactory,實例化所有的 bean。
10、第十個擴展點在 refresh->finishRefresh 方法裏,會調用 getLifecycleProcessor().onRefresh(),會調用 SmartLifecycle->start 方法。
11、第十一個擴展點也在 refresh->finishRefresh 方法裏,會調用 publishEvent 然後觸發 ApplicationListener 監聽 ContextRefreshedEvent 的事件。
2.7、最後的三個擴展點-> run 方法
12、第十二個擴展點在 run 方法裏,會調用 listeners.started 方法,SpringApplicationRunListener->started。
13、第十三個擴展點也在 run 方法裏,會調用 callRunners 方法,ApplicationRunner 或 CommandLineRunner 的 run 方法。
14、第十四個擴展點也在 run 方法裏,會調用 listeners.ready方法,SpringApplicationRunListener->ready。
3、結語
第一篇分析 SpringBoot 啓動源碼到這裏就結束了,我們這次的目標是找到 SpringBoot 有哪些地方可以自己進行代碼擴展的,其中不免有些遺漏的,歡迎各位補充。
目前看完還是有很多問題,比如 SpringBootApplication 註解的作用是什麼、Bean 的實例化流程是什麼、以及我們 Web 的 URL 請求是如何到方法上的...等等。
最後,這篇分析完的這些擴展點能幹些什麼呢?