@ Import annotations

@import is a major part of Spring’s Java annotation-based configuration. The @import annotation provides the @bean annotation functionality, as well as the original Spring ability to organize multiple scattered XML files based on the < Import > tag in the XML Configuration file, in this case the @Configuration class.

The functions of the @import annotation are explained separately below.

1. Introduce other @Configuration

Suppose you have the following interface and two implementation classes:

package com.test interface ServiceInterface { void test(); } class ServiceA implements ServiceInterface { @Override public void test() { System.out.println("ServiceA"); } } class ServiceB implements ServiceInterface { @Override public void test() { System.out.println("ServiceB"); }}Copy the code

ConfigA ‘ ‘@Import’ ‘ConfigB:

package com.test @Import(ConfigB.class) @Configuration class ConfigA { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceA() { return new ServiceA(); } } @Configuration class ConfigB { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceB() { return new ServiceB(); }}Copy the code

By means of ConfigA create AnnotationConfigApplicationContext ServiceInterface, see what kind of implementation:

public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);  ServiceInterface bean = ctx.getBean(ServiceInterface.class); bean.test(); }Copy the code

The output is: ServiceB. Prove that the @import class definition is loaded before itself.

2. Directly initialize beans of other classes

After Spring 4.2, @import can specify the entity class directly and load the class definition into the context. For example, changing @import from ConfigA in the above code to @import (serviceb.class) generates the ServiceB Bean into the container context, and then runs the main method with the output: ServiceB. Class definition loading to prove that @import takes precedence over itself.

3. Specify the implementation ImportSelector (and DefferredServiceImportSelector), used for personalized loading

AnnotationMetadata specifies a class that implements ImportSelector and dynamically loads the class via AnnotationMetadata properties. AnnotationMetadata is the attribute of the class in which the Import annotation is located (or, if the annotation class is annotated, extends to the non-annotated class in which the annotation class is applied).

You need to implement the selectImports method, which returns a String array of @Configuation or the fully qualified names of the concrete Bean class to be loaded.

package com.test; class ServiceImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata ImportingClassMetadata) {return new String[]{" com.test.configb "}; } } @Import(ServiceImportSelector.class) @Configuration class ConfigA { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceA() { return new ServiceA(); }}Copy the code

Run the main method again and output: ServiceB. Prove that the @import class definition is loaded before itself. AnnotationMetadata allows classes to be dynamically loaded using AnnotationMetadata. Such as:

package com.test; @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(ServiceImportSelector.class) @interface EnableService { String name(); } class ServiceImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata ImportingClassMetadata) {// importingClassMetadata here is for non-annotated classes that use @enableservice // Because 'AnnotationMetadata' is the annotation class attribute of 'Import', if the annotation class is annotated, Extends to non-annotation classes that apply this annotation class Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true); String name = (String) map.get("name"); if (Objects.equals(name, "B")) { return new String[]{"com.test.ConfigB"}; } return new String[0]; }}Copy the code

Add the @enableservice (name = “B”) annotation to ConfigA.

package com.test; @EnableService(name = "B") @Configuration class ConfigA { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceA() { return new ServiceA(); }}Copy the code

Run the main method again and output: ServiceB.

You can also implement the DeferredImportSelector interface so that the classes returned by selectImports are loaded last, rather than first, as with the @import annotation. Such as:

package com.test; class DefferredServiceImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true); String name = (String) map.get("name"); if (Objects.equals(name, "B")) { return new String[]{"com.test.ConfigB"}; } return new String[0]; }}Copy the code

Modify EnableService comments:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
    String name();
}
Copy the code

So ConfigA will take precedence over DefferredServiceImportSelector return ConfigB loading, execute the main method, output: ServiceA

4. Specify the implementation ImportBeanDefinitionRegistrar class for personalized loading

Like ImportSelector usage and use, but if we want to redefine the Bean, inject properties such as dynamic, change the type of Bean and Scope, etc., will be achieved by specifying ImportBeanDefinitionRegistrar class implements. Such as:

Define ServiceC

package com.test; class ServiceC implements ServiceInterface { private final String name; ServiceC(String name) { this.name = name; } @Override public void test() { System.out.println(name); }}Copy the code

Define ServiceImportBeanDefinitionRegistrar dynamically register ServiceC, modify EnableService

package com.test; @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(ServiceImportBeanDefinitionRegistrar.class) @interface EnableService { String name(); } class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true); String name = (String) map.get("name"); BeanDefinitionBuilder BeanDefinitionBuilder = BeanDefinitionBuilder. RootBeanDefinition (ServiceC. Class) / / increase the structural parameters .addConstructorArgValue(name); / / registered Bean registry. RegisterBeanDefinition (" serviceC ", beanDefinitionBuilder getBeanDefinition ()); }}Copy the code

And according to the back of the source code parsing can know, ImportBeanDefinitionRegistrar load after the @ Bean annotations, so to modify ConfigA removed which was @ ConditionalOnMissingBean annotations of Bean, Otherwise, the ServiceInterface for ConfigA must be generated

package com.test; @EnableService(name = "TestServiceC") @Configuration class ConfigA { // @Bean // @ConditionalOnMissingBean // public ServiceInterface getServiceA() { // return new ServiceA(); / /}}Copy the code

After that, run main and output: TestServiceC

@import relevant source code parsing

The @import annotation is loaded and parsed when BeanFactoryPostProcessor handles it:

AbstractApplicationContext refresh method

-> invokeBeanFactoryPostProcessors(beanFactory);

-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

-> registryProcessor.postProcessBeanDefinitionRegistry(registry);

We mean ConfigurationClassPostProcessor registryProcessor here

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)

-> processConfigBeanDefinitions(registry):

Public void processConfigBeanDefinitions (BeanDefinitionRegistry registry) {/ / omit some configuration check and set the logic / / according to @ Order annotations, Sort all @configuration classes configcandidates.sort ((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); // The @Configuration class Set<ConfigurationClass> alreadyParsed = new HashSet<>(configcandidates.size ()); Do {// Parse parser. Parse (candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); / / generated class definition reader reads class defines the if (this. Reader = = null) {this. Reader = new ConfigurationClassBeanDefinitionReader (registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); If (registry. GetBeanDefinitionCount () > candidateNames. Length) {/ / omit check whether there are other need to load the configuration of the logic of}} while (! candidates.isEmpty()); // omit subsequent cleanup logic}Copy the code

The logic of the parser. Parse (candidates) is mainly composed of org. Springframework. Context. The annotation. ConfigurationClassParser implementation, The function is to load an @import annotation and an immediate @import annotation. reader.loadBeanDefinitions(configClasses); Logic is mainly composed of org. Springframework. Context. The annotation. ConfigurationClassBeanDefinitionReader loadBeanDefinitionsForConfigurationC The lass method is implemented to convert the above parsed configuration to BeanDefinition, which is the Bean definition.

1. Load the @import annotation

org.springframework.context.annotation.ConfigurationClassParser

The first is the parse method

public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); Try {the if (bd instanceof AnnotatedBeanDefinition) {/ / parse here is actually calling the following the doProcessConfigurationClass analysis parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); }} / / the last process all ` DeferredImportSelector `, comply with the above mentioned ` DeferredImportSelector ` function enclosing deferredImportSelectorHandler. The process (); } @Nullable protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass SourceClass, Predicate<String> filter) throws IOException {// Process MemberClass code for '@Component' annotations... // Handle '@propertysource' annotation related code... // handle '@componentScan' annotations related code... // Handle '@import' annotations: processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // Handle '@importResource' annotations related code... // Handle the code associated with the '@bean' annotation... // Handle interface method related code... // Handle the parent code... }Copy the code

Use the getImports method to collect the relevant @import classes.

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException { Set<SourceClass> imports = new LinkedHashSet<>(); Set<SourceClass> visited = new LinkedHashSet<>(); // Recursively query all annotations and whether the annotation contains @import collectImports(sourceClass, imports, visited); return imports; } private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException {// Record whether the class has been scanned. If so, do not add it again. If (visited. Add (sourceClass)) {for (sourceClass annotation: sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); // For non-@import annotations, recursively find if they contain the @import annotation if (! annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); }} / / add @ Import annotations inside all of the configuration class imports. The addAll (sourceClass. GetAnnotationAttributes (Import) class) getName (), "value")); }}Copy the code

Once collected, it can be parsed.

2. Parse the @import annotation

The method of parsing is processImports

Private final ImportStack = new ImportStack(); private final ImportStack = new ImportStack(); / / record all ImportBeanDefinitionRegistrar private final Map < ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>(); Private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } // Rely on if (checkForCircularImports && isChainedImportOnStack(configClass)) {// Rely on if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else {// push this.importstack.push (configClass); try { for (SourceClass candidate : ImportCandidates) {if (candidate.isAssignable(ImportSelector. Class)) {// Implement class <? > candidateClass = candidate.loadClass(); / / create the Selector instance ImportSelector Selector. = ParserStrategyUtils instantiateClass (candidateClass ImportSelector. Class, this.environment, this.resourceLoader, this.registry); / / see if any filter Predicate < String > selectorFilter = selector. GetExclusionFilter (); if (selectorFilter ! = null) { exclusionFilter = exclusionFilter.or(selectorFilter); } // If it is DeferredImportSelector, Use deferredImportSelectorHandler processing the if (the selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else {// If not DeferredImportSelector, call selectImports to get the fully qualified name of the class to load, Recursive calls this method to parse the String [] importClassNames = selector. SelectImports (currentSourceClass. For getMetadata ()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); }} else if (candidate. IsAssignable (ImportBeanDefinitionRegistrar. Class)) {/ / processing ImportBeanDefinitionRegistrar interface implementation class Class<? > candidateClass = candidate.loadClass(); // Similarly, To create these ImportBeanDefinitionRegistrar instance ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry); / / add importBeanDefinitionRegistrar, Used for loading configClass behind. AddImportBeanDefinitionRegistrar (registrar, currentSourceClass for getMetadata ()); } else {// Handle @Configuration annotation class, Or generic, directly generate beans) / / add this class at the stack enclosing importStack. RegisterImport (currentSourceClass. For getMetadata (), candidate.getMetadata().getClassName()); / / recursive back doProcessConfigurationClass processing @ Configuration annotation class 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(); }}}Copy the code

In this way, all @import annotations related to @Conditional classes are loaded and parsed, which is a big recursive process.

3. Convert to BeanDefinition and register with the container

Org. Springframework. Context. The annotation. ConfigurationClassBeanDefinitionReader loadBeanDefinitionsForConfigurationClass method :

private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } // Complete the Import, Import BeanDefinition if (configclass.isimported ()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } BeanMethod: BeanMethod: BeanMethod: BeanMethod: BeanMethod: configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } / / @ ImportResource annotation load loadBeanDefinitionsFromImportedResources (configClass. GetImportedResources ()); / / loading ImportBeanDefinitionRegistrar loading Bean Definition loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }Copy the code

Through here as you can see, why said before @ Bean annotations of Bean will take precedence over ImportBeanDefinitionRegistrar the Bean returned loaded.