簡明易理解的@SpringBootApplication註解原始碼解析(包含面試提問)
歡迎關注文章系列 ,關注我
《提升能力,漲薪可待》
《面試知識,工作可待》
《實戰演練,拒絕996》
歡迎關注我部落格,原創技術文章第一時間推出
也歡迎關注公 眾 號【Ccww筆記】,同時推出
如果此文對你有幫助、喜歡的話,那就點個讚唄,點個關注唄!
《提升能力,漲薪可待篇》- @SpringBootApplication註解原始碼解析
一、@SpringBootApplication
的作用是什麼?
Q:springboot專案的啟動類上,都會有個註解@SpringBootApplication
,這個註解起到了什麼作用?
@SpringBootApplication
public class MicroServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MicroServiceApplication.class, args);
}
}
我們進入@SpringBootApplication
註解,發現它等價於三個註解:@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
1) @SpringBootConfiguration
就相當於 @Configuration
@Configuration
public @interface SpringBootConfiguration {
}
2) @EnabelAutoConfiguration
相當於將這兩個類的例項加入到容器中AutoConfigurationImportSelector.class
AutoConfigurationPackages.Registrar.class
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
-
AutoConfigurationImportSelector.class
的作用是,注入spring.factories檔案中EnableAutoConfiguration對應的類的例項,當然要經過spring.factories檔案中AutoConfigurationImportFilter對應的過濾器(OnBeanCondition
、OnClassCondition
、OnWebApplicationCondition
等等)的過濾。還要排除掉@EnableAutoConfiguration
中的exclude和excludeName具體見
ConfigurationClassParser
的getImports方法,其中呼叫了AutoConfigurationImportSelector
的process方法和selectImports方法。public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports(); }
AutoConfigurationImportSelector
實現了DeferredImportSelector
,所以它是解析@Configuration
的最後一步。DeferredImportSelector
可以和@Order
搭配使用。AutoConfigurationImportSelector
的意義在於引入其他包時,可以直接注入其他包的@Configuration
,當然需要在其他包的resources資料夾下,新建META-INF目錄,在META-INF目錄下新建spring.factories檔案,加入org.springframework.boot.autoconfigure.EnableAutoConfiguration = @Configuration標註的類的路徑 -
AutoConfigurationPackages.Registrar.class
的作用是,注入一個名稱為AutoConfigurationPackages的BasePackages.class
例項。這個例項的作用在於儲存自動掃描的包路徑,供以後使用(比如JPA 的entity掃描)public static void register(BeanDefinitionRegistry registry, String... packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition .getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); } } private static String[] addBasePackages( ConstructorArgumentValues constructorArguments, String[] packageNames) { String[] existing = (String[]) constructorArguments .getIndexedArgumentValue(0, String[].class).getValue(); Set<String> merged = new LinkedHashSet<>(); merged.addAll(Arrays.asList(existing)); merged.addAll(Arrays.asList(packageNames)); return StringUtils.toStringArray(merged); }
3) @ComponentScan
當然是將路徑下合適的類載入到容器中
Q:它為什麼要用到TypeExcludeFilter.class
和AutoConfigurationExcludeFilter.class
這兩個過濾器
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
-
這兩個過濾器在
ComponentScanAnnotationParser
的parse方法中,被加入public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { //此處加入了三個預設的includeFilter,一個用來篩選@Componment標記的類,一個用來篩選javax.annotation.ManagedBean標記的類,一個用來篩選JSR-330中javax.inject.Named標記的類(如果引入了JSR-330依賴注入標準的話,即引入javax.inject包) ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); ... for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } ... scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
也是在parse這個方法裡呼叫了 typeFiltersFor方法,對
TypeFilter.class
的實現類進行了例項化。(也就是說TypeExcludeFilter.class
AutoConfigurationExcludeFilter.class
AbstractTypeHierarchyTraversingFilter.class
都在這裡進行了例項化,但是沒有加入spring的bean池)private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) { List<TypeFilter> typeFilters = new ArrayList<>(); FilterType filterType = filterAttributes.getEnum("type"); for (Class<?> filterClass : filterAttributes.getClassArray("classes")) { switch (filterType) { case ANNOTATION: Assert.isAssignable(Annotation.class, filterClass, "@ComponentScan ANNOTATION type filter requires an annotation type"); @SuppressWarnings("unchecked") Class<Annotation> annotationType = (Class<Annotation>) filterClass; typeFilters.add(new AnnotationTypeFilter(annotationType)); break; case ASSIGNABLE_TYPE: typeFilters.add(new AssignableTypeFilter(filterClass)); break; case CUSTOM: Assert.isAssignable(TypeFilter.class, filterClass, "@ComponentScan CUSTOM type filter requires a TypeFilter implementation"); TypeFilter filter = BeanUtils.instantiateClass(filterClass, TypeFilter.class); ParserStrategyUtils.invokeAwareMethods( filter, this.environment, this.resourceLoader, this.registry); typeFilters.add(filter); break; default: throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType); } } for (String expression : filterAttributes.getStringArray("pattern")) { switch (filterType) { case ASPECTJ: typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader())); break; case REGEX: typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression))); break; default: throw new IllegalArgumentException("Filter type not supported with String pattern: " + filterType); } } return typeFilters; }
ComponentScanAnnotationParser
的parse方法中,scanner最後為什麼又加了一個AbstractTypeHierarchyTraversingFilter
呢?我們看一下它的match方法,發現是過濾掉啟動類,不讓它作為@configuration標記的候選類,避免再一次解析啟動類上的各種註解(因為它的兩個引數considerInherited和considerInterfaces在scanner.addExcludeFilter這條語句中,設定成了false,導致下面的判斷語句不生效)。public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { ... ClassMetadata metadata = metadataReader.getClassMetadata(); if (matchClassName(metadata.getClassName())) { return true; } if (this.considerInherited) { ... } if (this.considerInterfaces) { ... } return false; }
注:啟動類本身還是會注入到spring的bean池中的,具體見
SpringApplication
的load方法 -
ExcludeFilter在
ClassPathScanningCandidateComponentProvider
的isCandidateComponent方法中被使用。protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false; }
-
AutoConfigurationExcludeFilter
的作用是過濾掉會自動配置的配置類,避免重複@Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //如果這個類被@Configuration標註,且屬於自動載入的配置,那麼過濾它,避免重複 return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader); } private boolean isConfiguration(MetadataReader metadataReader) { return metadataReader.getAnnotationMetadata() .isAnnotated(Configuration.class.getName()); } private boolean isAutoConfiguration(MetadataReader metadataReader) { return getAutoConfigurations() .contains(metadataReader.getClassMetadata().getClassName()); } protected List<String> getAutoConfigurations() { if (this.autoConfigurations == null) { /** 從META-INF/spring.factories檔案中,找出EnableAutoConfiguration.class 多個jar包中,都存在spring.factories檔案 其中包含EnableAutoConfiguration.class的spring.factories檔案,位於spring-boot-autoconfigure **/ this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames( EnableAutoConfiguration.class, this.beanClassLoader); } return this.autoConfigurations; }
-
TypeExcludeFilter
的作用是載入spring bean池中所有針對TypeExcludeFilter的擴充套件,並迴圈遍歷這些擴充套件類呼叫其match方法public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) { //載入spring bean池中所有針對TypeExcludeFilter的擴充 Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory) .getBeansOfType(TypeExcludeFilter.class).values(); // 迴圈遍歷,呼叫其match方法 for (TypeExcludeFilter delegate : delegates) { if (delegate.match(metadataReader, metadataReaderFactory)) { return true; } } } return false; }
也歡迎關注微信公眾號【Ccww筆記】,原創技術文章第一時間推出
如果此文對你有幫助、喜歡的話,那就點個讚唄,點個關注唄!
相關文章
- 精盡Spring Boot原始碼分析 - 剖析 @SpringBootApplication 註解Spring Boot原始碼APP
- @SpringBootApplication註解Spring BootAPP
- SDWebImage原始碼解析之SDWebImageManager的註解Web原始碼
- Spring原始碼系列:註解說明Spring原始碼
- 使用 @SpringBootApplication 註解Spring BootAPP
- 清晰易懂TCP通訊原理解析(附demo、簡易TCP通訊庫原始碼、解決沾包問題等)C#版TCP原始碼C#
- Vue解析–如何應對面試官提問Vue面試
- Vue解析--如何應對面試官提問Vue面試
- 神秘又強大的@SpringBootApplication註解Spring BootAPP
- Spring5原始碼深度解析(一)之理解Configuration註解Spring原始碼
- Spring @Profile註解使用和原始碼解析Spring原始碼
- spring原始碼解析:元註解功能的實現Spring原始碼
- Spring原始碼深度解析(郝佳)-學習-原始碼解析-基於註解注入(二)Spring原始碼
- Spring 原始碼(7)Spring的註解是如何解析的?Spring原始碼
- 面試必備:SparseArray原始碼解析面試原始碼
- 面試必備:ArrayMap原始碼解析面試原始碼
- mybatis原始碼解析(四)--- MapperStatement的註冊MyBatis原始碼APP
- 完全解析!Bert & Transformer 閱讀理解原始碼詳解ORM原始碼
- 關於Java註解(annotation)的簡單理解Java
- flask 原始碼解析:簡介Flask原始碼
- 幣幣合約執行解析(包含部分原始碼)原始碼
- 帶你一起探究Retrofit 原始碼,讓你不再畏懼Retrofit的面試提問原始碼面試
- mybatis原始碼-註解sqlMyBatis原始碼SQL
- SAP Fiori @OData.publish 註解的工作原理解析
- 面試、筆試提問問題面試筆試
- Environment Switcher 原理解析(註解、Apt、反射、混淆)APT反射
- springboot系列文章之SpringBootApplication註解Spring BootAPP
- android面試——開源框架的原始碼解析Android面試框架原始碼
- maven下載原始碼,解決中文註釋為亂碼的問題Maven原始碼
- SpringBoot原始碼解析-@ConditionalOnXXX註解原理Spring Boot原始碼
- Emacs簡易操作說明(轉)Mac
- EOS 原始碼解析 股權證明的交易(TaPos) 的作用原始碼
- 深入理解Ribbon之原始碼解析原始碼
- 深入理解 Feign 之原始碼解析原始碼
- DI 原理解析 並實現一個簡易版 DI 容器
- 面試官:註解五問你怕了嗎?面試
- ElementUI 簡要原始碼解析——Basic篇UI原始碼
- 解決自己的提問