Concise and understandable @SpringBootApplication annotation source code analysis

Keywords: Spring SpringBoot

springApplication

1. What is the function of @SpringBootApplication?

There is a commentary @SpringBootApplication on the startup class of Q:springboot project. What role does this annotation play?

@SpringBootApplication
public class MicroServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MicroServiceApplication.class, args);
    }

}

We entered the @SpringBootApplication annotation and found that it was equivalent to three annotations: @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) @SpringBoot Configuration is equivalent to @Configuration

@Configuration
public @interface SpringBootConfiguration {

}

2)@Enabel AutoConfiguration is equivalent to adding instances of these two classes to the container AutoConfiguration ImportSelector.class AutoConfiguration Packages.Registrar.class.

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
  • AutoConfiguration ImportSelector. class is used to inject an instance of the class corresponding to Enable AutoConfiguration in the spring.factories file, which of course passes through the filter corresponding to AutoConfiguration ImportFilter in the spring.factories file (OnBeanCondition, OnClassCondition, OnWeb Application Conditions, etc.). Also exclude exclude and excludeName from @Enable AutoConfiguration

    See Configuration Class Parser's getImports method specifically, which calls the AutoConfiguration Import Selector's process method and selectImports method.

    public Iterable<group.entry> getImports() {
    			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    						deferredImport.getImportSelector());
    			}
    			return this.group.selectImports();
    		}
    

    AutoConfiguration Import Selector implements Deferred Import Selector, so it is the last step in parsing @Configuration. DeferredImportSelector can be used with @Order. The significance of AutoConfiguration Import Selector is to inject @Configuration directly into other packages when introducing other packages. Of course, you need to create META-INF directory under the resources folder of other packages, spring.factories file under the META-INF directory, and add the path of the class labeled org.spring framework.boot.autoconfigure.EnableAutoConfiguration = @Configuration.

  • AutoConfiguration Packages. Registrar. class injects a BasePackages.class instance named AutoConfiguration Packages. The purpose of this example is to save the package path for automatic scanning for later use (such as JPA entity scanning)

    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&lt;&gt;();
    		merged.addAll(Arrays.asList(existing));
    		merged.addAll(Arrays.asList(packageNames));
    		return StringUtils.toStringArray(merged);
    	}
    

3) @ComponentScan, of course, loads the appropriate classes under the path into the container

Q: why does it use two filters, TypeExcludeFilter.class and AutoConfigurationExcludeFilter.class?

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}
  • These two filters are added to the parse method of ComponentScan Annotation Parser

    public Set<beandefinitionholder> parse(AnnotationAttributes componentScan, final String declaringClass) {
        //Three default includeFilter s are added here, one for filtering @Componment tags, one for filtering javax.annotation.ManagedBean tags, and one for filtering javax.inject.Named tags in JSR-330 (introducing the javax.inject package if JSR-330 dependency injection criteria are introduced)
        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));
    	}
    
      ** Also called the typeFiltersFor method in the parse method to instantiate the implementation class of `TypeFilter.class'. (That is to say, `Type ExcludeFilter. class ``AutoConfiguration ExcludeFilter. class `AbstractType Hierarchy TraversingFilter. class ` are all instantiated here, but spring's bean pool is not added.)
    
    private List<typefilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
    		List<typefilter> typeFilters = new ArrayList&lt;&gt;();
    		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;
    	}
    

    In the parse method of ComponentScanAnnotationParser, why did scanner add a AbstractTypeHierarchyTraversingFilter at last? Looking at its match method, we find that it filters out the startup class from being a candidate class for the @configuration tag and avoids parsing the various annotations on the startup class again (because its two parameters, considerInherited and considerInterfaces, are set to false in the statement scanner.addExcludeFilter, which results in the following statement not valid).

    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;
    	}
    

    Note: The startup class itself will still be injected into the spring bean pool, as shown in the Spring Application load method

  • ExcludeFilter is used in the isCandidate Component method of ClassPathScanning Candidate Component Provider.

    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;
    	}
    
  • AutoConfiguration ExcludeFilter filters out configuration classes that are automatically configured to avoid duplication

    @Override
    	public boolean match(MetadataReader metadataReader,
    			MetadataReaderFactory metadataReaderFactory) throws IOException {
    		//If this class is tagged with @Configuration and belongs to an autoloaded configuration, filter it to avoid duplication
            return isConfiguration(metadataReader) &amp;&amp; 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) {
                /**
                Find EnableAutoConfiguration.class from the META-INF/spring.factories file
                There are spring.factories files in multiple jar packages
                It contains the spring.factories file of EnableAutoConfiguration.class, located in spring-boot-autoconfigure
                **/
    			this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(
    					EnableAutoConfiguration.class, this.beanClassLoader);
    		}
    		return this.autoConfigurations;
    	}
    
  • The function of TypeExcludeFilter is to load all the extensions for TypeExcludeFilter in the spring bean pool and iterate through these extension classes to invoke their match method.

    public boolean match(MetadataReader metadataReader,
    			MetadataReaderFactory metadataReaderFactory) throws IOException {
    		if (this.beanFactory instanceof ListableBeanFactory
    				&amp;&amp; getClass() == TypeExcludeFilter.class) {
                //Loading all extensions to TypeExcludeFilter in the spring bean pool
    			Collection<typeexcludefilter> delegates = ((ListableBeanFactory) this.beanFactory)
    					.getBeansOfType(TypeExcludeFilter.class).values();
                // Loop through, calling its match method
    			for (TypeExcludeFilter delegate : delegates) {
    				if (delegate.match(metadataReader, metadataReaderFactory)) {
    					return true;
    				}
    			}
    		}
    		return false;
    	}
    

</typeexcludefilter></string></annotation></annotation></typefilter></typefilter></beandefinitionholder></string></group.entry>

Posted by salathe on Wed, 09 Oct 2019 06:54:05 -0700