"Ability improvement, salary increase to wait" - easy to understand @ SpringBootApplication annotation source code analysis

Posted by bhinkel on Mon, 18 Nov 2019 11:59:34 +0100

Welcome to study together
Improve ability and raise salary
Interview knowledge, work to be done
Practice, no 996
If this article helps you and likes it, please give me a compliment!

Preface

Is it hard to get promoted at work?
Is it so hard to find a job interview?
Do you feel like you work overtime in 996 every day?

In order to get better promotion at work, we must keep the ability of learning at work, and the salary increase is just around the corner. Welcome to learn the series of "improving ability, salary increase is just around the corner"
Interview knowledge points should be summarized on the basis of learning in job search interview. Work is just around the corner. Welcome to learn [interview knowledge, work can wait] series together
Finally, should theoretical knowledge be practiced when it is well prepared? Welcome to learn the 996 series

springApplication

I. what is the function of @ SpringBootApplication?

Q: there will be an annotation @ SpringBootApplication on the startup class of the springboot project. What does this annotation do?

@SpringBootApplication
public class MicroServiceApplication {

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

}

We enter the @ SpringBootApplication annotation and find that it is 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) @ SpringBootConfiguration is equivalent to @ Configuration

@Configuration
public @interface SpringBootConfiguration {

}

2) @ enableautoconfiguration is equivalent to adding instances of these two classes to the container: autoconfigurationimportselector.class autoconfigurationpackages.registrar.class

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

}
  • The function of AutoConfigurationImportSelector.class is to inject an instance of the class corresponding to EnableAutoConfiguration into the spring.factors file, which, of course, needs to be filtered by the filter corresponding to AutoConfigurationImportFilter (onbean condition, OnClassCondition, OnWebApplicationCondition, etc.) in the spring.factors file. exclude and excludeName from @ EnableAutoConfiguration

    Specifically, see the getImports method of ConfigurationClassParser, which calls the process method and selectImports method of AutoConfigurationImportSelector.

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

    AutoConfigurationImportSelector implements the DeferredImportSelector, so it's the last step in parsing @ Configuration. DeferredImportSelector can be used with @ Order. The significance of AutoConfigurationImportSelector is that when importing other packages, you can directly inject @ Configuration of other packages. Of course, you need to create a new META-INF directory in the resources folder of other packages, a new spring.factories file in the META-INF directory, and add the path of the class marked by org.springframework.boot.autoconfigure.EnableAutoConfiguration = @Configuration

  • The role of AutoConfigurationPackages.Registrar.class is to inject a BasePackages.class instance named AutoConfigurationPackages. The function of this instance 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 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 in the parse method of componentscananannotationparser

    public Set<beandefinitionholder> parse(AnnotationAttributes componentScan, final String declaringClass) {
        //Three default include filters are added here, one for filtering @ component tags, one for filtering javax.annotation.ManagedBean tags, and one for filtering javax.inject.Named tags in JSR-330 (if the JSR-330 dependency injection standard is introduced, the javax.inject package is 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));
    	}
    

The typeFiltersFor method is also called in the parse method to instantiate the implementation class of TypeFilter.class. (that is to say, typeexcludefilter.class autoconfigurationexcludefilter.class abstracttypehierarchytraversingfilter.class has been instantiated here, but has not joined the bean pool of spring.)

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 componentscananannotationparser, why does scanner finally add an 'AbstractTypeHierarchyTraversingFilter'? When we look at its match method, we find that * * filters out the startup class, prevents it from being a candidate class for the @ configuration tag, and avoids parsing various annotations * * on the startup class again (because its two parameters, considerInherited and considerInterfaces, are set to false in the scanner.addExcludeFilter statement, which causes the following judgment statement not to take effect).
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 be injected into the bean pool of spring. For details, see the load method of spring application

  • The ExcludeFilter is used in the isCandidateComponent method of ClassPathScanningCandidateComponentProvider.

    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;
    	}
    
  • The function of AutoConfigurationExcludeFilter is to filter out the configuration classes that will be automatically configured and avoid repetition

    @Override
    	public boolean match(MetadataReader metadataReader,
    			MetadataReaderFactory metadataReaderFactory) throws IOException {
    		//If this class is labeled by @ Configuration and belongs to the auto loaded Configuration, filter it to avoid repetition
            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) {
                /**
                From the META-INF/spring.factories file, locate EnableAutoConfiguration.class
                spring.factories file exists 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 extensions for TypeExcludeFilter in spring bean pool, and loop through these extension classes to call their match methods

    public boolean match(MetadataReader metadataReader,
    			MetadataReaderFactory metadataReaderFactory) throws IOException {
    		if (this.beanFactory instanceof ListableBeanFactory
    				&amp;&amp; getClass() == TypeExcludeFilter.class) {
                //Load all extensions for TypeExcludeFilter in spring bean pool
    			Collection<typeexcludefilter> delegates = ((ListableBeanFactory) this.beanFactory)
    					.getBeansOfType(TypeExcludeFilter.class).values();
                // Loop traversal, 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>

Topics: Spring SpringBoot