First, annotation usage

The @import annotation is also used to register components with the container. There are three ways to use the @import annotation to quickly Import a component into the container

  1. The import@ConfigurationAnnotations used by the configuration class@Import(Component to be imported into container) : The container automatically registers the component, whose ID defaults to the full class name
  2. The importImportSelectorImplementation class: by implementationImportSelectorClasses that implementselectImportsMethod returns an array of the full class names of the components you want to import
  3. The importImportBeanDefinitionRegistrarImplementation class: by implementationImportBeanDefinitionRegistrarClasses that implementregisterBeanDefinitionsMethod to manually register beans into the container
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interfaceImport { Class<? >[] value(); }Copy the code

As you can see from the annotated source code, the @import annotation applies to classes, and the arguments can be arrays of type class. You can use the @import annotation to Import multiple components into a container at once

Ii. Case analysis

From the annotation usage above, there are three ways to Import components into a container using the @import annotation, and the annotation applies to methods that can Import more than one component at a time. Therefore, we will Import all three methods directly in the @import annotation. Use method 1 to inject the User class, method 2 to inject the Person class, and method 3 to inject the Animal class.

[1] Use @import for Configuration classes that Import @Configuration annotations

// Start the class by printing the Bean in the container to determine whether to inject
@Test
public void TestMain(a){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    String[] beanNames = applicationContext.getBeanDefinitionNames();
    for(String beanName : beanNames) { System.out.println(beanName); }}// The User to be injected
public class User {}/ / configuration class
@Configuration
@Import(User.class)     // Import the component with @import. ID defaults to the component's full class name
public class AppConfig {}Copy the code

By injecting User into the container with the @import annotation on the configuration class and running the startup class, you can see the User object in the container:

[2] Import the implementation class ImportSelector

Import ImportSelector implementation class need to implement ImportSelector class, custom logic returns the component to import, return the string array is the component to inject, add the following code:

ImportSelector implements the class
public class MyImportSelector implements ImportSelector {
    / * * *@descriptionGets the full class name * of the component to import into the container@author ONESTAR
     * @date 2021/2/25 15:49
     * @paramAnnotationMetadata: Current annotation@ImportAll annotation information for the annotation class *@throws
     * @return java.lang.String[]
     */
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"bean.Person"}; }}// The Person to be injected
public class Person {}/ / configuration class
@Configuration
@Import({User.class, MyImportSelector.class})     // Import the component with @import. ID defaults to the component's full class name
public class AppConfig {}Copy the code

Get the full class name of the component to be imported into the container in the ImportSelector implementation class. Here, the ImportSelector implementation class is configured in the configuration class using the @import annotation. Run the start class and you can see the Person object in the container:

【 3 】 import ImportBeanDefinitionRegistrar implementation class

Import ImportBeanDefinitionRegistrar implementation class needs to implement ImportBeanDefinitionRegistrar class, by the method of realizing registerBeanDefinitions manually registered Bean into the container, Add and modify the following code:

/ / ImportBeanDefinitionRegistrar implementation class
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        // Specify the name of the Bean
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Animal.class);
        beanDefinitionRegistry.registerBeanDefinition("Animal", beanDefinition); }}// Animal to be injected
public class Animal {}/ / configuration class
@Configuration
@Import({User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})     // Import the component with @import. ID defaults to the component's full class name
public class AppConfig {}Copy the code

Through ImportBeanDefinitionRegistrar to manually register to add Bean implementation class, and using the @ in the configuration class Import annotations configured, run to start the class, can see the container have Animal objects:

Third, source tracking

Reference: blog.csdn.net/mamamalulul…

Through @ Configuration annotations, will enter the doProcessConfigurationClass method, it is resolved appConfigure and inside the doProcessConfigurationClass method, There is a method to perform the @import annotation called processImports

this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
Copy the code

ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser: processImports ConfigurationClassParser

[1] getImports method

Enter the source code to look at the method, which is to get all the @import classes, as follows:

  1. Define a collection of visited visits as a sign of whether they have been judged
  2. The idea here is to get all the annotations on sourceClass and judge them one by one, if not @import, then recursively call the corresponding annotation until they’re all done
  3. Load the corresponding class name of the @Import Annotation in sourceClass and return
// Get all the classes in '@import'
private Set<ConfigurationClassParser.SourceClass> getImports(ConfigurationClassParser.SourceClass sourceClass) throws IOException {
    Set<ConfigurationClassParser.SourceClass> imports = new LinkedHashSet();
    Set<ConfigurationClassParser.SourceClass> visited = new LinkedHashSet();
    this.collectImports(sourceClass, imports, visited);
    return imports;
}

// Get all the annotations on sourceClass, if not @import, then recursively call the corresponding annotation until it's all done
private void collectImports(ConfigurationClassParser.SourceClass sourceClass, Set<ConfigurationClassParser.SourceClass> imports, Set<ConfigurationClassParser.SourceClass> visited) throws IOException {
    if (visited.add(sourceClass)) {
        Iterator var4 = sourceClass.getAnnotations().iterator();

        while(var4.hasNext()) {
            ConfigurationClassParser.SourceClass annotation = (ConfigurationClassParser.SourceClass)var4.next();
            String annName = annotation.getMetadata().getClassName();
            if(! annName.equals(Import.class.getName())) {this.collectImports(annotation, imports, visited);
            }
        }

        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); }}Copy the code

[2] processImports method

ProcessImports = processImports = processImports

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
    // The set of candidate classes ready for injection is returned empty
    if(! importCandidates.isEmpty()) {// Check for loop injection
        if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));
        } else {
            // configClass is added to ImportStack
            this.importStack.push(configClass);

            try {
                Iterator var6 = importCandidates.iterator();

                // Iterate over the injected candidate collection
                while(var6.hasNext()) {
                    ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();
                    Class candidateClass;
                    // If the class implements the ImportSelector interface
                    if (candidate.isAssignable(ImportSelector.class)) {
                        candidateClass = candidate.loadClass();
                        ImportSelector selector = (ImportSelector)ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
                        Predicate<String> selectorFilter = selector.getExclusionFilter();
                        if(selectorFilter ! =null) {
                            // Filter the injected classes
                            exclusionFilter = exclusionFilter.or(selectorFilter);
                        }

                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
                        } else {
                            // Call selectImports in selector to get the fully qualified name of the class to be injected
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);
                            this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                        }
                        / / if is ImportBeanDefinitionRegistrar configClass addImportBeanDefinitionRegistrar in advance on a map
                    } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    } else {
                        // If it is a common class
                        this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); }}}catch (BeanDefinitionStoreException var17) {
                throw var17;
            } catch (Throwable var18) {
                throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);
            } finally {
                this.importStack.pop(); }}}}Copy the code

The above approach is the core of the @import annotation, which can be summarized as follows:

  1. Determine the set of candidates for injectionimportCandidatesIf the value is empty, exit directly
  2. Circular injection check: Check isChainedImportOnStack. If true, add error in problemReporter and exit
  3. The currentconfigClassTo join theImportStackInside,ImportStackIs inheritedArrayDequeAnd implementedImportRegistry
  4. Iterate over the injected candidate set: YesgetImportsThe need to get insideimport(The three types of classes perform different logic)
    1. A class that implements the ImportSelector interface calls the getExclusionFilter() method. If it is not empty, it is filtered and then calledselectImportsMethod to get the fully qualified name of the class to inject. According to the class fully qualified name, get the class meta information. The processImports method is then recursively called
    2. To achieve theImportBeanDefinitionRegistrarInterface class, instantiate that class and put it into the collectionimportBeanDefinitionRegistrarsamong
    3. Class of common type (neither of the above), then it is treated as a configuration class, callprocessConfigurationClassMethod will eventually be put intoconfigurationClassesIn this set.
  5. If it isImportBeanDefinitionRegistrarType, again, instantiate an object and then add toimportBeanDefinitionRegistrarsInside, it’s going to be inConfigurationClassBeanDefinitionReaderIn this classloadBeanDefinitionsFromRegistrarsmethod-processed
  6. If the above two types are not, it is the original ordinary belt@ConfigurationClass is processed