在上一篇博客中分析了springBoot
啟動流程,大體的輪廓只是冰山一角。今天就來看一下springBoot
的亮點功能:自動化裝配功能。
先從@SpringBootApplication
開始。在啟動流程章節中,我們講述了SpringBoot2大致的啟動步驟,並進行了源碼詳解。但是在刷新容器這塊並未展開,refreshContext(context);簡單的一行代碼,背后卻做了太多事情。所以為了不喧賓奪主,本篇也盡量選取和注解@SpringBootApplication有關的方法講解。
springBoot啟動類加載
首先加載springBoot啟動類注入到spring容器中beanDefinitionMap中,看下prepareContext方法中的load方法:load(context, sources.toArray(new Object[0]));
跟進該方法最終會執行BeanDefinitionLoader的load方法:
private int load(Object source) { Assert.notNull(source, "Source must not be null"); //如果是class類型,啟用注解類型 if (source instanceof Class<?>) { return load((Class<?>) source); } //如果是resource類型,啟用xml解析 if (source instanceof Resource) { return load((Resource) source); } //如果是package類型,啟用掃描包,例如:@ComponentScan if (source instanceof Package) { return load((Package) source); } //如果是字符串類型,直接加載 if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); }
繼續跟進load(Class<?> source)
方法:
上述方法判斷啟動類中是否包含@component注解,可我們的啟動類並沒有該注解。繼續跟進會發現,AnnotationUtils判斷是否包含該注解是通過遞歸實現,注解上的注解若包含指定類型也是可以的。
啟動類中包含@SpringBootApplication注解,進一步查找到@SpringBootConfiguration注解,然后查找到@Component注解,最后會查找到@Component注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { }
在查找到@Component
注解后,表面該對象為spring bean,然后會將其信息包裝成 beanDefinitaion ,添加到容器的 beanDefinitionMap中。如下:
如此一來,我們的啟動類就被包裝成AnnotatedGenericBeanDefinition
了,后續啟動類的處理都基於該對象了。
@EnableAutoConfiguration
@SpringBootApplication注解中包含了自動配置的入口注解:
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {}
我們跟進去看看@EnableAutoConfiguration
@AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage
- 自動配置包注解
@Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}
@Import(AutoConfigurationPackages.Registrar.class):默認將主配置類(@SpringBootApplication)所在的包及其子包里面的所有組件掃描到Spring容器中。如下
@Order(Ordered.HIGHEST_PRECEDENCE) static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //默認將會掃描@SpringBootApplication標注的主配置類所在的包及其子包下所有組件 register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.<Object>singleton(new PackageImport(metadata)); } }
@Import(EnableAutoConfigurationImportSelector.class)
EnableAutoConfigurationImportSelector: 導入哪些組件的選擇器,將所有需要導入的組件以全類名的方式返回,這些組件就會被添加到容器中。
1 //EnableAutoConfigurationImportSelector的父類:AutoConfigurationImportSelector 2 @Override 3 public String[] selectImports(AnnotationMetadata annotationMetadata) { 4 if (!isEnabled(annotationMetadata)) { 5 return NO_IMPORTS; 6 } 7 try { 8 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader 9 .loadMetadata(this.beanClassLoader); 10 AnnotationAttributes attributes = getAttributes(annotationMetadata); 11 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 12 configurations = removeDuplicates(configurations); 13 configurations = sort(configurations, autoConfigurationMetadata); 14 Set<String> exclusions = getExclusions(annotationMetadata, attributes); 15 checkExcludedClasses(configurations, exclusions); 16 configurations.removeAll(exclusions); 17 configurations = filter(configurations, autoConfigurationMetadata); 18 fireAutoConfigurationImportEvents(configurations, exclusions); 19 return configurations.toArray(new String[configurations.size()]); 20 } 21 catch (IOException ex) { 22 throw new IllegalStateException(ex); 23 } 24 }
我們主要看第11行List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
會給容器中注入眾多的自動配置類(xxxAutoConfiguration),就是給容器中導入這個場景需要的所有組件,並配置好這些組件。獲取這些組件后,還要過濾一下這些組件,我們跟進去看看
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); //... return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { //從類路徑的META-INF/spring.factories中加載所有默認的自動配置類 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); //獲取EnableAutoConfiguration指定的所有值,也就是EnableAutoConfiguration.class的值 String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
SpringBoot啟動的時候從類路徑下的 META-INF/spring.factories中獲取EnableAutoConfiguration指定的值,並將這些值作為自動配置類導入到容器中,自動配置類就會生效,最后完成自動配置工作。EnableAutoConfiguration默認在spring-boot-autoconfigure這個包中,如下圖
最終有96個自動配置類被加載並注冊進Spring容器中
我們也可以將需要自動配置的Bean寫入這個文件
自定義starter
首先定義一個配置類模塊:
@Configuration @ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true) public class MyAutoConfiguration { static { System.out.println("myAutoConfiguration init..."); } @Bean public SimpleBean simpleBean(){ return new SimpleBean(); } }
然后定義一個starter模塊,里面無需任何代碼,pom也無需任何依賴,只需在META-INF下面建一個 spring.factories
文件,添加如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
spring.study.startup.bean.MyAutoConfiguration
如圖所示:
最后只需在啟動類項目的pom中引入我們的 starter 模塊即可。
原理
最終在AutoConfigurationImportSelector
解析spring.factories
文件:
springBoot為我們提供的配置類有180多個,但是我們不可能會全部引入。按條件注解 @Conditional或者@ConditionalOnProperty等相關注解進行判斷,決定是否需要裝配。
我們自定義的配置類也是以相同的邏輯進行裝配,我們指定了以下注解:
@ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
默認為 true,所以自定義的starter成功執行。