Three Uses and Source Interpretation of Spring Import

Keywords: Programming Spring Java xml

_Recent reviews of Spring Cloud-related sources have added enable s to each new starter, such as: @EnableDiscoveryClient, which registers applications with Eureka Server and pulls services owned by Eureka Server into a micro-service system.Click on the EnableDiscoveryClient source code and you will find the @import annotation inside.The source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

    /**
     * If true, the ServiceRegistry will automatically register the local server.
     */
    boolean autoRegister() default true;
}

What does this @import do and how does it work?How do we apply @import to import our custom classes in the project?

Usage of Import

Prior to Spring 3.0, our beans could inject classes into Spring IOC containers through xml profiles and scanning for classes under specific packages.After Spring 3.0, JavaConfig was provided, which described the meta-information of beans in IOC containers as java code.We can use @Configuration in conjunction with @Bean annotations to describe beans originally configured in xml files as java code.The @Import annotation provides the ability to @Bean annotations, as well as the ability to organize multiple scattered xml files with the <import>tag in the xml configuration file, where, of course, multiple scattered @Configurations are organized.Look at the source code for the @Import comment first:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

//Here's what it says you can use with Configuration, ImportSelector, ImportBeanDefinitionRegistrar, Oh or Bean, the regular component classes
    /**
     * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
     * or regular component classes to import.
     */
    Class<?>[] value();

}

From the source code, you can see that Import can be used with Configuration, @ImportSelector, ImportBeanDefinitionRegistrar. The following or means that Import can also be used as a normal Bean, but in a slightly different way, @Import can only be placed on classes, not methods.Let's see how to use it.There are several classes that we'll use in the following ways before we use them. Here's how to put these public classes together.

  • First, there is an entity class for User, which can be used as a parameter in Bean or not in actual tests. For the integrity of the example, I'll paste the code below:
package com.ivan.entity;

public class User {

    private Integer id;
    
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    
}

  • A UserService interface class is defined to represent Spring's Interface-oriented programming.The code is as follows:
package com.ivan.service;

import com.ivan.entity.User;

public interface UserService {
    
    public int save(User user);
    
}

  • The implementation class corresponding to the UserService interface is also the class we want to inject into the Spring IOC container with the following code:
package com.ivan.service.impl;

import com.ivan.entity.User;
import com.ivan.service.UserService;

public class UserServiceImpl implements UserService {

    public int save(User user) {
        System.out.println("The current method was called");
        return 1;
    }

}

  • Test class:
package com.ivan;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.ivan.config.Config;
import com.ivan.service.UserService;
import com.ivan.service.impl.UserServiceImpl;

/**
 * 
 * Functional description: 
 * 
 * @version 2.0.0
 * @author zhiminchen
 */
public class App {
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);   
        UserService userService = (UserService)context.getBean(UserServiceImpl.class);
        userService.save(null);
        context.close();
    }
}

Config.class is used in the test class App. The code here is different for different ways of using Import. Here's how Import differs in usage:

1:The easiest way to import a class

_This is the easiest way to add a class to the Spring IOC container by directly adding the class of the class to the value of Import. Config's code is as follows:

package com.ivan.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.ivan.service.impl.UserServiceImpl;

@Configuration
@Import(value={UserServiceImpl.class})
public class Config {

}

Start the test class and find that we have called the save method in our UserServiceImpl. The problem with injecting the class is that we can't inject parameters.That is, UserServiceImpl should provide a construct method without parameters.This is not a very useful way to inject classes inside Spring.The two most commonly used methods are the following.

2. Injecting classes into Spring IOC containers via ImportBeanDefinitionRegistrar

_Import annotation injects a class into the Spring IOC container by cooperating with the ImportBeanDefinitionRegistrar class.The source code for the ImportBeanDefinitionRegistrar class is as follows:

public interface ImportBeanDefinitionRegistrar {

    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry current bean definition registry
     */
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

From the code above, we can see that when injecting into the Spring IOC container, we must have passed the registry variable, and importingClassMetadata can get the metadata information of the class.Our custom UserServiceBeanDefinitionRegistrar class is defined as follows:

package com.ivan.bean.registrar;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

import com.ivan.service.impl.UserServiceImpl;

public class UserServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder userService = BeanDefinitionBuilder.rootBeanDefinition(UserServiceImpl.class);
        //You can inject it into the container by registry
        registry.registerBeanDefinition("userService", userService.getBeanDefinition());
    }

}

The Import class of the Config class needs to be changed to UserServiceBeanDefinitionRegistrar.classs.The code is as follows:

package com.ivan.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.ivan.bean.registrar.UserServiceBeanDefinitionRegistrar;

@Configuration(value="ivan_test")
@Import(value={UserServiceBeanDefinitionRegistrar.class})
public class Config {

}

It is clear that we can personalize our classes through ImportBeanDefinitionRegistrar, such as modifying the parameters that need to be passed in, or injecting a similar set of classes through ImportBeanDefinitionRegistrar.There are BeanDefinitionRegistry objects that control the definition of beans in the Spring IOC container, and it is much easier to do anything you want.

3. Injecting Bean s via ImportSelector

The above instance injected by ImportBeanDefinitionRegistrar requires us to manipulate the BeanDefinitionRegistry object, whereas by ImportSelector does not manipulate the BeanDefinitionRegistry object, just tell the container that we need to inject the full class name of the class.The source code for the ImportSelector class is as follows:

public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

The code above shows that you need to return a String array to the container, and the parameters you pass in can get annotated metadata information. Now let's simulate how the enable s we see in the Cloud code are implemented by ImportSelector.First we need to customize a comment with the following source code:

package com.ivan.enable;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

import com.ivan.select.UserServiceImportSelect;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(UserServiceImportSelect.class)
public @interface EnableUserService {
    
    String name ();

}

EnableUserService This is a comment that introduces UserServiceImportSelect through @Import. The injected logic is in the class UserServiceImportSelect. Our comment also defines a name property. This is just for testing purposes. In practice, you can define the properties you need, and then configure them differently in the specific ImportSelect according to their values.The code for the UserServiceImportSelect property is as follows:

package com.ivan.select;

import java.util.Map;
import java.util.Map.Entry;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import com.ivan.enable.EnableUserService;
import com.ivan.service.impl.UserServiceImpl;

public class UserServiceImportSelect implements ImportSelector{

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        
       Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableUserService.class.getName(), true);
       for(Entry<String, Object> entry : map.entrySet()){
           System.out.println("key is : " + entry.getKey() + " value is : " + entry.getValue());
       }
        
       return new String[]{UserServiceImpl.class.getName()};
    }

}

Meta information using the EnableUserService annotation can be obtained from the importingClassMetadata property.It also returns the name of the class we need to inject so that it can be injected into the container. The code for the Config class is as follows:

package com.ivan.config;

import org.springframework.context.annotation.Configuration;

import com.ivan.enable.EnableUserService;

@Configuration()
@EnableUserService(name="ivan_test")
public class Config {

}

With the three steps above, we can customize the enable annotations and inject the corresponding beans, and by the way ImportBeanDefinitionRegistrar does, we can also customize the annotations and inject the classes we need.ImportBeanDefinitionRegistrar should be the most flexible way to do this.

Source Code Interpretation

_With the above several applications, you should be able to master the application of the @Import annotation.But how to inject classes inside Spring IOC? Here's the source code for the class inside Spring IOC.The key is how to read the source code to find the entry to the source code. Here's a little trick. We can find the key code through the All Hierarchy call chain of the IDE. We know that the @Import annotation, however godly it is, will call the selectImports method in the ImportSelector class.This way we can find the following call relationships, as shown in the following figure:

 
 
image.png
 

_The BeanFactoryPostProcessor method is called during the Spring IOC container loading process to handle operations such as modifying the BeanDefinition.Looking at the call chain, we need to look at the ConfigurationClassPostProcessor class, and we can see from the name that it must have something to do with the @Configuration comment.The following is a comment on the key code for ConfigurationClassPostProcessor:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
        PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

         /**
           *This method is called when the container is initially enabled.
          **/
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        int factoryId = System.identityHashCode(beanFactory);
        if (this.factoriesPostProcessed.contains(factoryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + beanFactory);
        }
        this.factoriesPostProcessed.add(factoryId);
        if (!this.registriesPostProcessed.contains(factoryId)) {
            // BeanDefinitionRegistryPostProcessor hook apparently not supported...
            // Simply call processConfigurationClasses lazily at this point then.
                        //Configuration must have been handled in this way
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
        }

        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }


    /** The comment here already says that Configuration is handled
     * Build and validate a configuration model based on the registry of
     * {@link Configuration} classes.
     */
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
        String[] candidateNames = registry.getBeanDefinitionNames();

        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {

//Configuration, Component, ComponentScan, Import, ImportResource annotated classes are added to the configCandidates container
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
//If there are no classes mentioned above, return directly
        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }

//Sort sunburned classes by Order
        // Sort by previously determined @Order value, if applicable
        Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
            @Override
            public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
            }
        });

        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
                 //Specific resolution is through ConfigurationClassParser
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
        do {
//Call the parse method of ConfigurationClassParser for parsing
            parser.parse(candidates);
            parser.validate();

            Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(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());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

            candidates.clear();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<String>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());

        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
        if (sbr != null) {
            if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
                sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
            }
        }

        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }


}

The analysis above ultimately resolves the Import class on top of the ConfigurationClassParser class, which we can also see from the call diagram above. The following comment on the key code of this class (code intercepts only part of the source code, not in the order in the source code, just for ease of reading):

class ConfigurationClassParser {

      /**
        *This is the entry method for parsing, and the parameter passed in is a Class class with special annotations
      **/
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
                      //The processConfigurationClass method is called regardless of whether bd belongs to the following three cases
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    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);
            }
        }

        processDeferredImportSelectors();
    }

//The following three methods are used to handle different BeanDefinition types, but they all end up calling the processConfigurationClass method
    protected final void parse(String className, String beanName) throws IOException {
        MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
        processConfigurationClass(new ConfigurationClass(reader, beanName));
    }

    protected final void parse(Class<?> clazz, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(clazz, beanName));
    }

    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName));
    }

//This handles the class class class of the Configuration label
    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }

        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
                //Processing Cconfiguration Repeat import here
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
                    if (configClass.equals(it.next())) {
                        it.remove();
                    }
                }
            }
        }

        // Recursively process the configuration class and its superclass hierarchy.
                //Here we loop through the annotated classes above to get the SourceClass object
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

/**
**This method handles specific annotation classes, @Configuration @Import, etc.
*/
    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {

        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass);

        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        // Process any @ComponentScan annotations
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }
                 //In this method, the Import annotation is handled.
        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);

        // Process any @ImportResource annotations
        if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
            AnnotationAttributes importResource =
                    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);

        // Process superclass, if any
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                return sourceClass.getSuperClass();
            }
        }

        // No superclass -> processing is complete
        return null;
    }

//Handle specific Import s here
    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, 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) {
//See the familiar ImportSelector here
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
//This adds DeferredImportSelector to the deferredImportSelectors collection class and calls the interface-defined method in the parse method
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
//Processing ImportBeanDefinitionRegistrar here,
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
//Using import as a Configuration is also our easiest way to apply it
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            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();
            }
        }
    }

}

The above overview examines how the @Import comment is handled in Spring, tracking only the logic of how ImportSelector is handled, with more detailed comments in the code.The other two methods of analysis are much the same.To understand it as a whole, there are several processes:

  • The refresh method of AbstractApplicationContext is called when the Spring IOC container is initially enabled
  • Various BeanFactoryPostProcessor s are called in refresh, including the ConfigurationClassPostProcessor that we need to focus on.
  • Configuration ClassPostProcessor handles not only the @Configuration annotation, but also the @Import annotation.
  • The final processing is through the ConfigurationClassParser class, which handles the various cases of Import
  • Import has three ways of importing, and from the code we can see how it is handled differently.


Author: Beautiful Times TT
Link: https://www.jianshu.com/p/7eb0c2b214a7
Source: Short Book
Copyright belongs to the author.For commercial reprinting, please contact the author for authorization. For non-commercial reprinting, please indicate the source.

Posted by thomashw on Thu, 19 Mar 2020 17:55:50 -0700