Java Springboot自動裝配原理詳解
Debug路線圖
說多都是淚,大家看圖。
讓我們從run說起
用了這么多年的的Springboot,這個 run() 方法到底做了些什么事呢?
@SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } }
歸屬
run() 方法歸屬于 SpringApplication.class
對象,所以在調(diào)用run() 方法前,需要先實(shí)例化 SpringApplication.class
對象:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
SpringApplication.class
對象實(shí)例化時,都做了些什么事呢?
這里主要看需要注意兩個方法:①getSpringFactoriesInstances()
和 ②deduceMainApplicationClass()
/** * 實(shí)例化時,實(shí)際調(diào)用的構(gòu)造方法 */ public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 這里將spring. this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories(); // 設(shè)置初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 設(shè)置監(jiān)聽器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
①getSpringFactoriesInstances()
方法主要加載整個應(yīng)用程序中的 spring.factories
文件,將文件的內(nèi)容放到緩存對象中,方便后續(xù)獲取使用。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { // 初次加載,cache中獲取不到數(shù)據(jù),所以為null Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",這個配置類眼熟吧 // 加載配置類 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中 cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
②deduceMainApplicationClass()
方法返回了啟動類的類信息:
小結(jié)
實(shí)例化SpringApplication.class
對象完成了兩件事:
1.加載整個應(yīng)用程序中的 spring.factories
文件,將文件的內(nèi)容放到緩存對象中,方便后續(xù)獲取使用。
2.返回了啟動類的類信息。
run
有了SpringApplication.class
對象實(shí)例對象,接下來就可以調(diào)用run() 方法。
在run() 方法中,我們主要關(guān)注兩個方法prepareContext()
和 refreshContext()
public ConfigurableApplicationContext run(String... args) { // 省略部分代碼 try { prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); } // 省略部分代碼
prepareContext()
方法準(zhǔn)備上下文環(huán)境,通過調(diào)用load()方法加載啟動類,為獲取啟動類上的注解做準(zhǔn)備;
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()); } // 這里會判斷啟動類不是一個groovy閉包也不是一個匿名類 if (isEligible(source)) { // 注冊讀取啟動類的注解信息 // 注意,這里將啟動類型注冊為AnnotatedBeanDefinition類型,后面parse()解析時會用到。 this.annotatedReader.register(source); } }
refreshContext()
方法最終調(diào)用了AbstractApplicationContext.class
類的 refresh()
,這里相信看過spring源碼的小伙伴都很熟悉 refresh()
這個方法。
自動裝配操作的主戰(zhàn)場主要是在 ①invokeBeanFactoryPostProcessors()
方法,①調(diào)用了②invokeBeanDefinitionRegistryPostProcessors()
方法,②調(diào)用了ConfigurationClassPostProcessor.class
的③postProcessBeanDefinitionRegistry()
方法,③調(diào)用了 ④processConfigBeanDefinitions()
方法;
④ processConfigBeanDefinitions()
方法中:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); // 這里會循環(huán)匹配到啟動類,并且添加到上面的configCandidates集合中。 for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // ... // 解析每一個標(biāo)注了@Configuration注解的類,啟動類上的@SpringBootApplication就包含了@Configuration注解 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); // 開始解析 parser.parse(candidates); parser.validate(); } // ============ 分割線 ================= /** * 為了方便閱讀,這里將parse()方法接入 */ public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 在前面prepareContext()方法中的load()方法中已經(jīng)說過,所以這會進(jìn)入這個判斷解析 if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // ... } // 注意??!這里,parse()解析完成后,會回到這里執(zhí)行process()方法; this.deferredImportSelectorHandler.process(); }
進(jìn)入判斷后parse()
方法會接著調(diào)用 ①processConfigurationClass()
方法,①調(diào)用②doProcessConfigurationClass()
方法;
②doProcessConfigurationClass()
中又開始對注解進(jìn)行進(jìn)一步的解析,包括@PropertySource、@ComponentScan、@Import
(咱們看這個)、@ImportResource、@Bean,解析之前,會通過getImports()
方法調(diào)用collectImports()
方法,統(tǒng)計出被@Import標(biāo)注的類型信息;
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { // ... // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // ... // ============ 分割線 ================= private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException { Set<SourceClass> imports = new LinkedHashSet<>(); Set<SourceClass> visited = new LinkedHashSet<>(); collectImports(sourceClass, imports, visited); return imports; } private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException { if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.equals(Import.class.getName())) { // 注意看這里,自調(diào)用遞歸查詢 collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } }
getImports()
方法查詢結(jié)果展示:
當(dāng)parse()
方法解析完成 @Import 注解后(這里忘記的小伙伴,可看看上面的parse()方法,我有代碼注釋),接著開始調(diào)用①process()
方法,①中調(diào)用②processGroupImports()
方法,②中接著調(diào)用 ③grouping.getImports()
方法,③調(diào)用DeferredImportSelector.Group
接口的 ④process()
方法,這里我們看它的實(shí)現(xiàn)類 AutoConfigurationImportSelector.class
實(shí)現(xiàn)的 ④process()
方法(這里需要留意一下,一會還會回來用到),④調(diào)用了 ⑤getAutoConfigurationEntry(),⑤
中調(diào)用了⑥getCandidateConfigurations()
方法;
重點(diǎn)來了:經(jīng)過上面的一系列方法調(diào)用,終于來到這個方法,相信大家在許多博客里都又看到過 ⑥getCandidateConfigurations() 這個方法,但又沒有說清楚具體是怎么調(diào)用到這個方法的,只是說了這個類會得到待配置的class的類名集合等等;
在⑥這個方法中,顯示通過 ⑦getSpringFactoriesLoaderFactoryClass()
這個方法返回了一個EnableAutoConfiguration.class
注解對象,然后又通過調(diào)用 ⑧loadFactoryNames(),⑧
又調(diào)用了 ⑨loadSpringFactories();
⑧⑨方法看著是不是比較眼熟? 是的,我們在初始化SpringApplication對象時,曾調(diào)用過這兩個方法,在調(diào)用⑨時,將 spring.factories 文件的內(nèi)容放到cache緩存對象中。
@Override protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // getSpringFactoriesLoaderFactoryClass()這個方法返回了一個EnableAutoConfiguration.class注解對象 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } // ===========================分割線=========================== private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } // ...
此時的cache對象中存在EnableAutoConfiguration對象,size=131個:
這131個就是 spring.factories
文件中的自動裝配配置項:
當(dāng)然,這里面有許多我們沒有用到類信息也被裝配了進(jìn)來,這里不要著急接著往下看,裝配完成后回到了⑤getAutoConfigurationEntry()
方法中,且返回了一個List< String>的一個配置類信息集合,接著又做了些什么事?
從上圖可以看出,131個配置信息,經(jīng)過過濾移除后,最終變成13個需要使用的,拿到最終配置信息,(愣著干嘛,趕緊撒花呀!),到這里自動裝配過程基本上就結(jié)束了。
這里的結(jié)束是指自動裝配過程結(jié)束,也就是我們 refresh()中的invokeBeanFactoryPostProcessors() 方法執(zhí)行結(jié)束,當(dāng)然這個方法還做很多別的事,但是本文只關(guān)注自動裝配相關(guān),完成此方法后并不表示類就已經(jīng)實(shí)例化完成,這里只是將類信息裝配到了spring容器中,后續(xù)會有別的方法完成類的實(shí)例化。(實(shí)例化看它:finishBeanFactoryInitialization())
再說說注解
@SpringBootApplication 是的沒錯,這個注解大家都熟悉,springboot 項目啟動類上都有:
@SpringBootApplication 中包含了兩個注解:
- @EnableAutoConfiguration(重點(diǎn)):啟用 SpringBoot 的自動配置機(jī)制;
- @ComponentScan: 掃描被@Component (@Service,@Controller)注解的 bean,注解默認(rèn)會掃描該類所在的包下所有的類;
- @SpringBootConfiguration:允許在上下文中注冊額外的 bean 或?qū)肫渌渲妙悾?/li>
三個注解中,自動裝配的核心 @EnableAutoConfiguration 就是這個注解:
@EnableAutoConfiguration
注解通過 Spring 提供的 @Import
注解導(dǎo)入了 AutoConfigurationImportSelector.class
類(@Import 注解可以導(dǎo)入配置類或者 Bean 到當(dāng)前類中),這個類的作用在上也說過(獲取spring.factories文件中待配置的class的類名集合)。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
RestTemplat中關(guān)于getForobject方法的使用
這篇文章主要介紹了RestTemplat中關(guān)于getForobject方法的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07深入淺出的學(xué)習(xí)Java ThreadLocal
本文會基于實(shí)際場景介紹ThreadLocal如何使用以及內(nèi)部實(shí)現(xiàn)機(jī)制。 具有很好的參考價值,下面跟著小編一起來看下吧2017-02-02SpringBoot AOP處理請求日志打印功能代碼實(shí)例
這篇文章主要介紹了SpringBoot AOP處理請求日志打印功能代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03java?9大性能優(yōu)化經(jīng)驗總結(jié)
這篇文章主要介紹了java?9大性能優(yōu)化經(jīng)驗總結(jié),包括:Java代碼優(yōu)化,數(shù)據(jù)庫優(yōu)化,分布式緩存,異步化,Web前段,搜索引擎優(yōu)化等需要的朋友可以參考下2023-02-02spring mvc中@RequestBody注解的作用說明
這篇文章主要介紹了spring mvc中@RequestBody注解的作用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08