Java Springboot自動裝配原理詳解
Debug路線圖
說多都是淚,大傢看圖。
讓我們從run說起
用瞭這麼多年的的Springboot,這個 run() 方法到底做瞭些什麼事呢?
@SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } }
歸屬
run() 方法歸屬於 SpringApplication.class
對象,所以在調用run() 方法前,需要先實例化 SpringApplication.class
對象:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
SpringApplication.class
對象實例化時,都做瞭些什麼事呢?
這裡主要看需要註意兩個方法:①getSpringFactoriesInstances()
和 ②deduceMainApplicationClass()
/** * 實例化時,實際調用的構造方法 */ 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(); // 設置初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 設置監聽器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
①getSpringFactoriesInstances()
方法主要加載整個應用程序中的 spring.factories
文件,將文件的內容放到緩存對象中,方便後續獲取使用。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { // 初次加載,cache中獲取不到數據,所以為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()
方法返回瞭啟動類的類信息:
小結
實例化SpringApplication.class
對象完成瞭兩件事:
1.加載整個應用程序中的 spring.factories
文件,將文件的內容放到緩存對象中,方便後續獲取使用。
2.返回瞭啟動類的類信息。
run
有瞭SpringApplication.class
對象實例對象,接下來就可以調用run() 方法。
在run() 方法中,我們主要關註兩個方法prepareContext()
和 refreshContext()
public ConfigurableApplicationContext run(String... args) { // 省略部分代碼 try { prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); } // 省略部分代碼
prepareContext()
方法準備上下文環境,通過調用load()方法加載啟動類,為獲取啟動類上的註解做準備;
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()
方法最終調用瞭AbstractApplicationContext.class
類的 refresh()
,這裡相信看過spring源碼的小夥伴都很熟悉 refresh()
這個方法。
自動裝配操作的主戰場主要是在 ①invokeBeanFactoryPostProcessors()
方法,①調用瞭②invokeBeanDefinitionRegistryPostProcessors()
方法,②調用瞭ConfigurationClassPostProcessor.class
的③postProcessBeanDefinitionRegistry()
方法,③調用瞭 ④processConfigBeanDefinitions()
方法;
④ processConfigBeanDefinitions()
方法中:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); // 這裡會循環匹配到啟動類,並且添加到上面的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)); } } // ... // 解析每一個標註瞭@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()方法中已經說過,所以這會進入這個判斷解析 if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // ... } // 註意!!這裡,parse()解析完成後,會回到這裡執行process()方法; this.deferredImportSelectorHandler.process(); }
進入判斷後parse()
方法會接著調用 ①processConfigurationClass()
方法,①調用②doProcessConfigurationClass()
方法;
②doProcessConfigurationClass()
中又開始對註解進行進一步的解析,包括@PropertySource、@ComponentScan、@Import
(咱們看這個)、@ImportResource、@Bean,解析之前,會通過getImports()
方法調用collectImports()
方法,統計出被@Import標註的類型信息;
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())) { // 註意看這裡,自調用遞歸查詢 collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } }
getImports()
方法查詢結果展示:
當parse()
方法解析完成 @Import 註解後(這裡忘記的小夥伴,可看看上面的parse()方法,我有代碼註釋),接著開始調用①process()
方法,①中調用②processGroupImports()
方法,②中接著調用 ③grouping.getImports()
方法,③調用DeferredImportSelector.Group
接口的 ④process()
方法,這裡我們看它的實現類 AutoConfigurationImportSelector.class
實現的 ④process()
方法(這裡需要留意一下,一會還會回來用到),④調用瞭 ⑤getAutoConfigurationEntry(),⑤
中調用瞭⑥getCandidateConfigurations()
方法;
重點來瞭:經過上面的一系列方法調用,終於來到這個方法,相信大傢在許多博客裡都又看到過 ⑥getCandidateConfigurations() 這個方法,但又沒有說清楚具體是怎麼調用到這個方法的,隻是說瞭這個類會得到待配置的class的類名集合等等;
在⑥這個方法中,顯示通過 ⑦getSpringFactoriesLoaderFactoryClass()
這個方法返回瞭一個EnableAutoConfiguration.class
註解對象,然後又通過調用 ⑧loadFactoryNames(),⑧
又調用瞭 ⑨loadSpringFactories();
⑧⑨方法看著是不是比較眼熟? 是的,我們在初始化SpringApplication對象時,曾調用過這兩個方法,在調用⑨時,將 spring.factories 文件的內容放到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
文件中的自動裝配配置項:
當然,這裡面有許多我們沒有用到類信息也被裝配瞭進來,這裡不要著急接著往下看,裝配完成後回到瞭⑤getAutoConfigurationEntry()
方法中,且返回瞭一個List< String>的一個配置類信息集合,接著又做瞭些什麼事?
從上圖可以看出,131個配置信息,經過過濾移除後,最終變成13個需要使用的,拿到最終配置信息,(愣著幹嘛,趕緊撒花呀!),到這裡自動裝配過程基本上就結束瞭。
這裡的結束是指自動裝配過程結束,也就是我們 refresh()中的invokeBeanFactoryPostProcessors() 方法執行結束,當然這個方法還做很多別的事,但是本文隻關註自動裝配相關,完成此方法後並不表示類就已經實例化完成,這裡隻是將類信息裝配到瞭spring容器中,後續會有別的方法完成類的實例化。(實例化看它:finishBeanFactoryInitialization())
再說說註解
@SpringBootApplication 是的沒錯,這個註解大傢都熟悉,springboot 項目啟動類上都有:
@SpringBootApplication 中包含瞭兩個註解:
- @EnableAutoConfiguration(重點):啟用 SpringBoot 的自動配置機制;
- @ComponentScan: 掃描被@Component (@Service,@Controller)註解的 bean,註解默認會掃描該類所在的包下所有的類;
- @SpringBootConfiguration:允許在上下文中註冊額外的 bean 或導入其他配置類;
三個註解中,自動裝配的核心 @EnableAutoConfiguration 就是這個註解:
@EnableAutoConfiguration
註解通過 Spring 提供的 @Import
註解導入瞭 AutoConfigurationImportSelector.class
類(@Import 註解可以導入配置類或者 Bean 到當前類中),這個類的作用在上也說過(獲取spring.factories文件中待配置的class的類名集合)。
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Spring中的spring.factories文件用法(Spring如何加載第三方Bean)
- SpringBoot是如何實現自動配置的你知道嗎
- SpringBoot自動配置深入探究實現原理
- springboot自動配置原理以及spring.factories文件的作用詳解
- SpringBoot自動配置特點與原理詳細分析