簡明易理解的@SpringBootApplication註解原始碼解析(包含面試提問)

Ccww_發表於2020-02-05

歡迎關注文章系列 ,關注我
《提升能力,漲薪可待》
《面試知識,工作可待》
《實戰演練,拒絕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對應的過濾器(OnBeanConditionOnClassConditionOnWebApplicationCondition等等)的過濾。還要排除掉@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.classAutoConfigurationExcludeFilter.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筆記】,原創技術文章第一時間推出

如果此文對你有幫助、喜歡的話,那就點個讚唄,點個關注唄!

相關文章