Spring refresh - invokeBeanFactoryPostProcessors - resolve @Import

Blog post directory


The @Import annotation brings a lot of flexibility to Spring bean creation, which greatly simplifies the use of Spring because of its encapsulation of configuration. @Enable* in Spring is basically implemented by @Import (add @Import annotation on @Enable to import other configuration classes)

How to use @Import

Eight, @Import analysis of spring ioc

Usually use @Import on a configuration class to import other configuration classes

  • Annotated with @Configuration, marked as FULL configuration class
  • There is no @Configuration annotation, but there is @Component, @ComponentScan, @Import, @ImportResource, or a method annotated with @Bean annotation in the class, marked as a LITE configuration class

Three ways to use @Import

  • Import a configuration class directly (normal classes can also be imported after 4.2)
  • Import the implementation class of the ImportSelector interface, parse a set of classes returned by the selectImports method as BeanDefinition and register
  • Import the implementation class of the ImportBeanDefinitionRegistrar interface, build and register the BeanDefinition by yourself

Which @Imports can be resolved

  • @Import on the configuration class
  • @Import inside other annotations on the configuration class, and @Import inside [email protected] annotations recursively from the configuration class

If the @Import being scanned imports a non-configured class, but there is an @Import on that class, it will also be resolved to

Note that classes imported by @Import will not be registered as BeanDefinitions and will not produce singleton beans

import configuration class

@EnableWebMvc

@Retention ( RetentionPolicy . RUNTIME ) 
@Target ( ElementType . TYPE ) 
@Documented 
@Import ( DelegatingWebMvcConfiguration . class ) 
public @ interface  EnableWebMvc  { }
  • 1
  • 2
  • 3
  • 4
  • 5

ImportSelector

@EnableAsync

@Target ( ElementType . TYPE ) 
@Retention ( RetentionPolicy . RUNTIME ) 
@Documented 
@Import ( AsyncConfigurationSelector . class ) 
public @ interface  EnableAsync  { 
    Class < ?  extends  Annotation >  annotation ( )  default Annotation . class ; 
    boolean  proxyTargetClass ( )  default  false ; 
    AdviceModemode ( )  default AdviceMode . PROXY ; 
    int  order ( )  default Ordered . LOWEST_PRECEDENCE ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
public  class  AsyncConfigurationSelector  extends  AdviceModeImportSelector < EnableAsync >  { 
    private  static  final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = 
            "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration" ; 
    @Override 
    public String [ ]  selectImports ( AdviceMode adviceMode )  { 
        switch  ( adviceMode )  { 
            case PROXY : 
                return  new  String [ ]  {ProxyAsyncConfiguration . class . getName ( )  } ; 
            case ASPECTJ : 
                return  new  String [ ]  { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME } ; 
            default : 
                return null ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

The main function of the selectImports method of ImportSelector is to collect the configuration classes that need to be imported. A set of class-qualified names returned by the method will be parsed as configuration classes (walking through the complete process of parsing configuration classes will parse @PropertySource, @ComponentScan on this class) , @Import, @ImportSource, @Bean methods, etc.), ImportSelector itself is not registered as a BeanDefinition

Before calling the selectImports method of the ImportSelector, if the imported ImportSelector also implements EnvironmentAware, BeanFactoryAware, BeanClassLoaderAware, ResourceLoaderAware, then these will be taken and set to the corresponding properties of the ImportSelector

DeferredImportSelector delay import

@EnableAutoConfiguration

@Target ( { ElementType . TYPE } ) 
@Retention ( RetentionPolicy . RUNTIME ) 
@Documented 
@Inherited 
@SpringBootConfiguration 
@EnableAutoConfiguration 
@ComponentScan ( excludeFilters =  { 
	@Filter ( type = FilterType . CUSTOM , classes =  { TypeExcludeFilter . class } ) ,  
	@Filter ( type = FilterType .CUSTOM , classes =  { AutoConfigurationExcludeFilter . class } ) } 
) 
public @interface SpringBootApplication  { } _ 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
@Target ( { ElementType . TYPE } ) 
@Retention ( RetentionPolicy . RUNTIME ) 
@Documented 
@Inherited 
@AutoConfigurationPackage 
@Import ( { AutoConfigurationImportSelector . class } ) 
public @ interface  EnableAutoConfiguration  { 
    String ENABLED_OVERRIDE_PROPERTY =  "spring.boot.enableautoconfiguration" ; 
    Class < ? > [ ]  exclude ( ) default  { } ; 
    String [ ]  excludeName ( )  default  { } ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public  class  AutoConfigurationImportSelector  implements  DeferredImportSelector , BeanClassLoaderAware , ResourceLoaderAware , BeanFactoryAware , EnvironmentAware , Ordered { 
	// ... 
    public Class < ?  extends  Group >  getImportGroup ( )  { 
        return AutoConfigurationImportSelector . AutoConfigurationGroup . class ; 
    } 
	// ... 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

The DeferredImportSelector interface is a sub-interface of the ImportSelector interface. The configuration class introduced by this interface will parse other annotations and configuration classes in Spring (including but not limited to the configuration class introduced by ImportSelector, excluding the automatic configuration class, that is, the configuration class in the spring.factories file. ) and then parsed, depending on this design, the automatic configuration of SpringBoot can be realized. When the automatic configuration class is parsed, it will judge (@Conditional) whether the user-configured bean already exists in the Spring container according to the conditions. If it exists, the automatic configuration will not be executed. . This interface can also be used with the interface Ordered or @Order to define the priority of multiple DeferredImportSelector selectors

It also provides grouping function. First call the getImportGroup method of DeferredImportSelector to determine whether there is a custom group. If there is one, call the selectImports method of the group. If not, call the selectImports method of DeferredImportSelector. The selectImports method can be sorted

ImportBeanDefinitionRegistrar

@EnableAspectJAutoProxy

@Target ( ElementType . TYPE ) 
@Retention ( RetentionPolicy . RUNTIME ) 
@Documented 
@Import ( AspectJAutoProxyRegistrar . class ) 
public @ interface  EnableAspectJAutoProxy  { 
    boolean  proxyTargetClass ( )  default  false ; 
    boolean  exposeProxy ( )  default  false ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
class  AspectJAutoProxyRegistrar  implements  ImportBeanDefinitionRegistrar  { 
    @Override 
    public  void  registerBeanDefinitions ( 
            AnnotationMetadata importingClassMetadata , BeanDefinitionRegistry registry )  { 
        AopConfigUtils . registerAspectJAnnotationAutoProxyCreatorIfNecessary ( registry ) ; 
        AnnotationAttributes enableAspectJAutoProxy = 
                AnnotationConfigUtils . attributesFor ( importingClassMetadata , EnableAspectJAutoProxy .class ) ; 
        if  ( enableAspectJAutoProxy . getBoolean ( "proxyTargetClass" ) )  { 
            AopConfigUtils . forceAutoProxyCreatorToUseClassProxying ( registry ) ; 
        } 
        if  ( enableAspectJAutoProxy . getBoolean ( "exposeProxy" ) )  { 
            AopConfigUtils . forceAutoProxyCreatorToExposeProxy ( registry ) ; 
        } 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

The ImportBeanDefinitionRegistrar interface provides two default implementations of the registerBeanDefinitions method, which can be overridden to customize BeanDefinitions and register

Process of parsing @Import

  • Starting from the current configuration class, recursively search for @Import annotations on the class, as well as @Import annotations in [email protected] annotations on the class (the annotation class names starting with java are filtered out). Get all the classes imported by the @Import annotation. For example The configuration class is currently parsed, @Import(A.class) on the configuration class, @Import(B.class) on A, and an @Xxxxx annotation on the configuration class. In this annotation @Import(C.class), finally What I got is A.class, C.class
  • Traverse the imported classes obtained
    • If the imported class is ImportSelector (import selector: used to select the class to be imported)
      • If the imported class is deferred import DeferredImportSelector (deferred import selector: used to select the class to be imported, the resolution of the imported class will be delayed), the imported selector will be stored in the deferredImportSelectors of ConfigurationClassParser. Where to deal with later
      • If the imported class is not a lazy import, recursively import the imported class and then the imported class. At the end of the recursive parsing, the class that is neither ImportSelector nor ImportBeanDefinitionRegistrar will be reached
    • If the imported class is ImportBeanDefinitionRegistrar (Imported BeanDefinition Registrar: used to register the BeanDefinition that needs to be imported), add this ImportBeanDefinitionRegistrar to the importBeanDefinitionRegistrars of configClass. In the ConfigurationClassBeanDefinitionReader#loadBeanDefinitions process after parse, its method is traversed and called to register the BeanDefinition
    • If the imported class is neither ImportSelector nor ImportBeanDefinitionRegistrar, parse it as a configuration class, and follow the same process of parsing @Configuration. Instead of generating and registering BeanDefinition directly, it is first saved in the import of importStack of ConfigurationClassParser

Detailed process description

ConfigurationClassPostProcessor#processConfigBeanDefinitions

public  void  processConfigBeanDefinitions ( BeanDefinitionRegistry registry )  { 
		List < BeanDefinitionHolder > configCandidates =  new  ArrayList < > ( ) ;
	
	// ...

	// Start parsing the configuration class 
	// Parse the @PropertySource, @ComponentScan, @Import, @ImportResource, @Bean methods of the 
	configuration class one by one Parsing 
	// @Import imported classes will be divided into 3 types, 
	// Delayed in ImportSelector is temporarily stored, non-delayed is recursively parsed, 
	// ImportBeanDefinitionRegistrar is temporarily stored, 
	// None will be treated as configuration classes Go through the configuration class parsing process 
	// The xml imported by @ImportResource is also temporarily stored 
	// The methods annotated by @Bean are also temporarily stored 
	parser . parse ( candidates ) ;

	// Process all the above temporarily stored in a unified way 
	// Get the parsed configuration class 
	Set in the parser < ConfigurationClass > configClasses =  new  LinkedHashSet < > ( parser . getConfigurationClasses ( ) ) ; 
	configClasses . removeAll ( alreadyParsed ) ;

	// Read the model and create bean definitions based on its content 
	if  ( this . reader == null )  { 
		this . reader =  new  ConfigurationClassBeanDefinitionReader ( 
				registry ,  this . sourceExtractor ,  this . resourceLoader ,  this . environment , 
				this . importBeanNameGenerator , parser . getImportRegistry ( ) ) ; 
	}

	// Read and register the BeanDefinition from the parsed configuration class with a reader 
	// ImportBeanDefinitionRegistrar's subsequent registration BeanDefinition is here 
	// the subsequent processing of DeferredImportSelector has not found 
	this . reader . loadBeanDefinitions ( configClasses ) ;
	
	// ...

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

ConfigurationClassParser#doProcessConfigurationClass parses the @Import section

// Process any @Import annotations 
// Process @Import annotation 
processImports ( configClass , sourceClass ,  getImports ( sourceClass ) , filter ,  true ) ;
  • 1
  • 2
  • 3

ConfigurationClassParser#getImports

Starting from the current configuration class, recursively search for @Import annotations on the class, as well as @Import annotations in [email protected] annotations on the class (the annotation class names starting with java are filtered out). Get all the classes imported by the @Import annotation. For example The configuration class is currently parsed, @Import(A.class) on the configuration class, @Import(B.class) on A, and an @Xxxxx annotation on the configuration class. In this annotation @Import(C.class), finally What I got is A.class, C.class

private Set < SourceClass >  getImports ( SourceClass sourceClass )  throws IOException { 
	Set < SourceClass > imports =  new  LinkedHashSet < > ( ) ; 
	Set < SourceClass > visited =  new  LinkedHashSet < > ( ) ; 
	collectImports ( sourceClass , imports , visited ) ; 
	returnimports ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
private  void  collectImports ( SourceClass sourceClass , Set < SourceClass > imports , Set < SourceClass > visited ) 
		throws IOException {

	// Get the imported class of the @Import annotation on sourceClass, 
	// and the imported class of the @Import annotation introduced in other annotations on sourceClass, 
	// and the @Import annotation introduced in other annotations in other annotations on sourceClass The imported class 
	// and the imported class of the @Import annotation introduced in other annotations in other annotations on sourceClass 
	//... 
	// Recursive layer by layer 
	if  ( visited . add ( sourceClass ) )  { 
		for  ( SourceClass annotation : sourceClass . getAnnotations ( ) )  { 
			String annName = annotation . getMetadata ( ) . getClassName ( ); 
			if  ( ! annName . equals ( Import . class . getName ( ) ) )  {
				
				// Recursively current method 
				collectImports ( annotation , imports , visited ) ; 
			} 
		} 
		imports . addAll ( sourceClass . getAnnotationAttributes ( Import . class . getName ( ) ,  "value" ) ) ; 
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

ConfigurationClassParser#processImports

private  void  processImports ( ConfigurationClass configClass , SourceClass currentSourceClass , 
			Collection < SourceClass > importCandidates , Predicate < String > exclusionFilter , 
			boolean checkForCircularImports )  {

	if  ( importCandidates . isEmpty ( ) )  { 
		return ; 
	}

	if  ( checkForCircularImports &&  isChainedImportOnStack ( configClass ) )  { 
		this . problemReporter . error ( new  CircularImportProblem ( configClass ,  this . importStack ) ) ; 
	} 
	else  { 
		this . importStack . push ( configClass ) ; 
		try  { 
			for  ( SourceClass candidate : importCandidates ) {

				// What is imported is ImportSelector (import selector: used to select the class to be imported) 
				// The judgment is whether the candidate component class is ImportSelector or whether the candidate component class is the parent class of ImportSelector 
				if  ( candidate . isAssignable ( ImportSelector . class ) )  { 
					// Candidate class is an ImportSelector -> delegate to it to determine imports 
					Class < ? > candidateClass = candidate . loadClass ( ) ;
					
					// Instantiate this ImportSelector and inject 4 Aware 
					ImportSelector selectors if necessary = ParserStrategyUtils . instantiateClass ( candidateClass , ImportSelector . class , 
							this . environment ,  this . resourceLoader ,  this . registry ) ; 
					Predicate < String > selectorFilter = selector . getExclusionFilter ( ) ;

					if  ( selectorFilter != null )  { 
						exclusionFilter = exclusionFilter . or ( selectorFilter ) ; 
					}

					// If it is a delayed import of DeferredImportSelector (deferred import selector: used to select the class to be imported, the parsing of the imported class will be delayed) 
					// The imported selector will be stored in the deferredImportSelectors of ConfigurationClassParser 
					if  ( selector instanceof  DeferredImportSelector )  { 
						this . deferredImportSelectorHandler . handle ( configClass ,  ( DeferredImportSelector ) selector ) ; 
					}

					// not lazy import 
					else  { 
						String [ ] importClassNames = selector . selectImports ( currentSourceClass . getMetadata ( ) ) ; 
						Collection < SourceClass > importSourceClasses =  asSourceClasses ( importClassNames , exclusionFilter ) ;

						// Recursively import the imported class of this class and then the imported class 
						// The last of the recursive analysis must be a class that is neither ImportSelector nor ImportBeanDefinitionRegistrar 
						processImports ( configClass , currentSourceClass , importSourceClasses , exclusionFilter ,  false ) ; 
					} 
				}

				// ImportBeanDefinitionRegistrar (Imported BeanDefinition Registrar: used to register BeanDefinitions that need to be imported) 
				else  if  ( candidate . isAssignable ( ImportBeanDefinitionRegistrar . class ) )  { 
					// Candidate class is an ImportBeanDefinitionRegistrar -> 
					// delegate to it to register additional bean definitions 
					Class < ? > candidateClass = candidate . loadClass ( ) ;

					// Instantiate this ImportBeanDefinitionRegistrar and inject 4 Aware if necessary 
					ImportBeanDefinitionRegistrar registrar = 
							ParserStrategyUtils . instantiateClass ( candidateClass , ImportBeanDefinitionRegistrar . class , 
									this . environment ,  this . resourceLoader ,  this . registry ) ;

					// Add this ImportBeanDefinitionRegistrar to the importBeanDefinitionRegistrars of configClass 
					configClass . addImportBeanDefinitionRegistrar ( registrar , currentSourceClass . getMetadata ( ) ) ; 
				}

				// The imported one is neither ImportSelector nor ImportBeanDefinitionRegistrar 
				// Parse it as a configuration class, and follow the same process of parsing @Configuration 
				// The BeanDefinition is not directly generated and registered, but is first saved in the import of the importStack of the ConfigurationClassParser 
				else  { 
					// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> 
					// process it as an @Configuration class 
					System . out . println ( this . importStack . imports ) ;

					// Add the imported class information to the import of the importStack of the ConfigurationClassParser 
					this . importStack . registerImport ( 
							currentSourceClass . getMetadata ( ) , candidate . getMetadata ( ) . getClassName ( ) ) ; 
					System . out . println ( this . importStack . imports ) ;

					// Parse the imported class as a configuration class, follow the same process of parsing @Configuration 
					processConfigurationClass ( candidate . asConfigClass ( configClass ) , exclusionFilter ) ; 
				} 
			} 
		} 
		catch  ( BeanDefinitionStoreException ex )  { 
			throw ex ; 
		} 
		catch  ( Throwable ex )  { 
			throw  new  BeanDefinitionStoreException ( 
					"Failed to process import candidates for configuration class ["  + 
					configClass .getMetadata ( ) . getClassName ( )  +  "]" , ex ) ; 
		} 
		finally  { 
			this . importStack . pop ( ) ; 
		} 
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

Tags: Spring refresh - invokeBeanFactoryPostProcessors - resolve @Import

java

Related: Spring refresh - invokeBeanFactoryPostProcessors - resolve @Import