SpringBoot?啟動流程追蹤方法分享
1、初始化 SpringApplication
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(); this.bootstrapRegistryInitializers = new ArrayList<>( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
在完成初始化工作后,可以看到設(shè)置了如下屬性:bootstrapRegistryInitializers:
initializers:
listeners:
這些屬性咋來的上一篇文章中有提到過,會掃描 spring-boot、spring-boot-autoconfigure、spring-beans 包里面 resource 目錄下 META-INF/spring.factories 文件進行加載,如果你想添加自己的配置,也可以在自己項目的 resource 目錄下添加配置。
2、加載 spring.factories
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; }
加載結(jié)果如下所示:
以后有些地方加載類的時候,就會直接從緩存取了。
3、環(huán)境準備前的工作(run 方法代碼片段)
long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass);
在 createBootstrapContext 方法里面會調(diào)用 bootstrapRegistryInitializers 的 initializer 方法,不過 SpringBoot 該屬性沒值。然后會調(diào)用 getRunListeners 方法加載 SpringApplicationRunListeners,該值同樣也是從 spring.factories 文件進行加載的。該 listeners(SpringApplicationRunListeners) 下的 SpringApplicationRunListener 只有一個: EventPublishingRunListener
然后會調(diào)用該類的 starting 方法,會觸發(fā) ApplicationStartingEvent 事件,該事件會被 SpringApplication 下的 listeners 監(jiān)聽。如果你感興趣的話,可以看看 8 個 ApplicationListener 干了什么!
4、準備環(huán)境(run->prepareEnvironment)
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties."); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader()); environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
這里是先創(chuàng)建一個默認的 Servlet 環(huán)境,然后為該環(huán)境配置參數(shù),在 configureEnvironment ->configurePropertySources 方法中可以看到會從命令行參數(shù)和 SpringApplication 的 defaultProperties 屬性獲取可配置參數(shù)。環(huán)境準備好后,會執(zhí)行 listeners.environmentPrepared 方法,上文提到過,該方法只有一個實現(xiàn)類,調(diào)用該方法會觸發(fā) ApplicationEnvironmentPreparedEvent 事件,同樣也會被監(jiān)聽到。該方法目前就看這兩個就行了,其他的方法不知道在哪兒用的,看了也說不明白。
5、配置 Banner 和 上下文
Banner printedBanner = printBanner(environment); context = createApplicationContext();
如果想要知道怎么自定義 Banner,可以看 printBanner,通過創(chuàng)建 banner.txt 文本格式或 banner.png、banner.gif、banner.gif 等圖片格式文件,可實現(xiàn)自定義 banner,文件默認放在 resource 目錄下就行,如果不嫌麻煩的話也可以自定義 banner 存放目錄。接下來是 context 上下文,這在后面會經(jīng)常用到,它會使用默認的 contextFactory 來創(chuàng)建 context,并且它是通過 loadSpringFactories 方法來獲取的,其實現(xiàn)類在 spring.factories 里配置的是 AnnotationConfigServletWebServerApplicationContext。
6、準備上下文(run->prepareContext)
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); bootstrapContext.close(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof AbstractAutowireCapableBeanFactory) { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences); if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context)); // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
這里調(diào)用了 applyInitializers 方法,之前 SpringApplication 加載了 7 個 ApplicationContextInitializer,這里會調(diào)用每個 initializer 的 initialize 方法。接著就調(diào)用 listeners 的 contextPrepared 方法,還是之前的 EventPublishingRunListener,該方法會觸發(fā) 8 個 ApplicationListener 監(jiān)聽 ApplicationContextInitializedEvent 事件。在這之后的 bootstrapContext.close 方法也會 BootstrapContextClosedEvent 事件。然后想 set、add、log 啥的可以直接跳過,beanFactory.registerSingleton 方法可以點進去看看,不過也是 add 啥的,這些其實都是為后面實質(zhì)性的操作做準備,后面可以再追溯數(shù)據(jù)來源。接著看看 load 方法,load 方法的 source 來源一個是 primarySources,另一個是 sources,都是 SpringApplication 的屬性,該方法可以加載 Bean,并且該方法細節(jié)也是蠻多的,這里先記著,后面再看。最后就是 listeners.contextLoaded 方法了,該方法會觸發(fā) ApplicationPreparedEvent 事件。
7、添加上下文銷毀線程(refreshcontext...->addRuntimeShutdownHook)
void addRuntimeShutdownHook() { try { Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook")); } catch (AccessControlException ex) { // Not allowed in some environments } } @Override public void run() { Set<ConfigurableApplicationContext> contexts; Set<ConfigurableApplicationContext> closedContexts; Set<Runnable> actions; synchronized (SpringApplicationShutdownHook.class) { this.inProgress = true; contexts = new LinkedHashSet<>(this.contexts); closedContexts = new LinkedHashSet<>(this.closedContexts); actions = new LinkedHashSet<>(this.handlers.getActions()); } contexts.forEach(this::closeAndWait); closedContexts.forEach(this::closeAndWait); actions.forEach(Runnable::run); }
這里在線程末尾會執(zhí)行上下文的 closeAndWait 方法,以及支持自定義的 actions。
8、收尾工作
在刷新完 context 之后,會執(zhí)行 listeners.started、ready 方法,分別會觸發(fā) ApplicationStartedEvent、ApplicationReadyEvent 事件。同樣會被 8 個 ApplicationListener 監(jiān)聽到。另外還有一個 callRunners 方法值得注意,任何實現(xiàn)了 ApplicationRunner、CommandLineRunner 接口的實現(xiàn)類都會得到執(zhí)行。
9、加載 Bean(load)
前面提到過 load 方法也是一個值得注意的方法,他可以通過好幾種方式注冊 Bean:
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()); }
首先是 load(Class<?> source) 方法:它會判斷本地有沒有 groovy 環(huán)境,然后 source 對象是 GroovyBeanDefinitionSource 類或其子類的實例時,就實例化它,然后將 loader 對象的 bean 方法返回的 Bean 添加到 groovyReader 中。然后就判斷其是否有資格注冊為 Bean。
private void load(Class<?> source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans()); } if (isEligible(source)) { this.annotatedReader.register(source); } }
然后是 load(Resource source) 方法:總之它會從 grovvy 文件或者 xml 文件中注冊 Bean。
private void load(Resource source) { if (source.getFilename().endsWith(".groovy")) { if (this.groovyReader == null) { throw new BeanDefinitionStoreException("Cannot load Groovy beans without Groovy on classpath"); } this.groovyReader.loadBeanDefinitions(source); } else { if (this.xmlReader == null) { throw new BeanDefinitionStoreException("Cannot load XML bean definitions when XML support is disabled"); } this.xmlReader.loadBeanDefinitions(source); } }
然后是 load(Package source) 方法:總之它會從 package 里注冊 Bean。
private void load(Package source) { this.scanner.scan(source.getName()); }
最后就是 load(CharSequence source) 方法:它就很有意思了,它會嘗試將其作為以上三種方式進行加載。
private void load(CharSequence source) { String resolvedSource = this.scanner.getEnvironment().resolvePlaceholders(source.toString()); // Attempt as a Class try { load(ClassUtils.forName(resolvedSource, null)); return; } catch (IllegalArgumentException | ClassNotFoundException ex) { // swallow exception and continue } // Attempt as Resources if (loadAsResources(resolvedSource)) { return; } // Attempt as package Package packageResource = findPackage(resolvedSource); if (packageResource != null) { load(packageResource); return; } throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'"); }
10、總結(jié)
這篇文章我們了解了自動裝配的工作方式,也就是 spring.factories。然后就是 Banner 是如何打印的、context 環(huán)境準備完畢后如何執(zhí)行自定義代碼,context 的銷毀工作以及最后的兩種 runner 怎么使用。本文著重介紹了 load 方法通過幾種方式注冊 Bean的,包括 Groovy、xml等文件方式、Package包、Class類、CharSequence字符串等方式進行注冊。最后遺留了一個刷新上下文 refresh 方法沒有分析,這也是一個很重要的方法。
到此這篇關(guān)于SpringBoot 啟動流程追蹤方法分享的文章就介紹到這了,更多相關(guān)SpringBoot 啟動流程追蹤內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Security 表單登錄功能的實現(xiàn)方法
這篇文章主要介紹了Spring Security 表單登錄,本文將構(gòu)建在之前簡單的 Spring MVC示例 之上,因為這是設(shè)置Web應(yīng)用程序和登錄機制的必不可少的。需要的朋友可以參考下2019-06-06java遍歷途中修改數(shù)據(jù)及刪除數(shù)據(jù)的方法總結(jié)
在使用java的集合類遍歷數(shù)據(jù)的時候,在某些情況下可能需要對某些數(shù)據(jù)進行刪除,下面這篇文章主要給大家介紹了關(guān)于java遍歷途中修改數(shù)據(jù)及刪除數(shù)據(jù)的方法總結(jié),需要的朋友可以參考下2023-10-10Springboot整合Mybatis和SQLite的詳細過程
這篇文章主要介紹了Springboot整合Mybatis和SQLite的詳細過程,本文通過圖文示例相結(jié)合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-07-07