Basic usage

For the @Configuration annotation, please refer to the spring website.

Concept of pre –

Before analyzing the @Configuration source code, let’s take a look at some of the processing methods involved in the source code.

What is a configuration class

We know that the @Configuration class acts as a Configuration class. In fact, in addition to @Configuration, the injected class can also be considered a Configuration class in the following cases:

  1. When a class is decorated with @Configuration, @Component, @ComponentScan, @import, @importResource
  2. Spring considers a class to be a configuration class when it has methods decorated with @bean annotations

ConfigurationClassPostProcessor

@ the Configuration of annotation processing, are implemented through enhancer ConfigurationClassPostProcessor, The Processor is in PostProcessorRegistrationDelegate. InvokeBeanFactoryPostProcessors (..) Method is registered and executed. Instructions for this method are given in the previous article.

Take a look at the class declaration:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessorAnd,PriorityOrdered.ResourceLoaderAware.BeanClassLoaderAware.EnvironmentAware {
    / /...
}
Copy the code

Through the definition of the class, we can found that ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor and PriorityOrdered interface at the same time, it will see in the source code analysis below.

Full and lite

In addition to the @Configuration injected class, which is a Configuration class, there are two other methods. The differences are as follows:

  • The @Configuration modified Configuration class, identified in the source code by a property value full, represents the complete Configuration class
  • The other two configuration classes are identified in the source code by the attribute value lite, which represents a simplified version of the configuration class

Source code analysis

Follow the source code through a test code to understand the implementation principle of @Configuration

Configuration class: myconfig.java

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

@Configuration
public class MyConfig {
    @Bean
    public Person person(a) {
        return newPerson(); }}Copy the code

Injection class: Person.java

public class Person {
    public Person(a) {}}Copy the code

Test the main class: ApplicationSimpleConfiguration. Java

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ApplicationSimpleConfiguration {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println(context.getBean("person")); }}Copy the code

The above code is the most basic use of @Configuration, to analyze the source code

Said earlier, is handled by ConfigurationClassPostProcessor @ the Configuration, And it is the Processor in PostProcessorRegistrationDelegate. InvokeBeanFactoryPostProcessors (..) Register, concrete is in process BeanDefinitionRegistryPostProcessor logic block, The first call invokeBeanDefinitionRegistryPostProcessors (currentRegistryProcessors, registry); Method, and the code looks like this (simplified) :

# PostProcessorRegistrationDelegate.java

public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List
       
         beanFactoryPostProcessors)
        {

    Set<String> processedBeans = new HashSet<>();

    if (beanFactory instanceof BeanDefinitionRegistry) {
        
        / /... Omit other code
        

        // First, invoke ...
        / / here find ConfigurationClassPostProcessor, and stored in the array
        / / about ConfigurationClassPostProcessor when stored, the following detailed analysis
        / / [1]
        String[] postProcessorNames = 
            beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);  

        for (String ppName : postProcessorNames) {

            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                / / by getBean, instantiated ConfigurationClassPostProcessor into the container
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);

        // Complete the call processing logic
        / / this emphasizes the place, to use based on @ Configuration annotations and @ bean into the object, the type of processor for ConfigurationClassPostprocessor, and execute here
        
        / / [2]
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);              

        currentRegistryProcessors.clear();

        / /... Omit any other code that is not relevant to the content of this article
    } else {
        / /... Omit any other code that is not relevant to the content of this article
    }
    
    / /... Omit any other code that is not relevant to the content of this article
}

Copy the code

The annotations [1] and [2] in the source code posted above are important to note. First look at this line at [1] :

String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);
Copy the code

This line of code can get name: org. Springframework. Context. The annotation. InternalConfigurationAnnotationProcessor enhancer. The string is actually corresponds to the front said ConfigurationClassPostProcessor, it here to get the string value, is where I join in? This string is defined in AnnotationConfigUtils, an abstract class.

public abstract class AnnotationConfigUtils {

    / /... Omit other code

    public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalConfigurationAnnotationProcessor";

    public static final String AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME =
		"org.springframework.context.annotation.internalAutowiredAnnotationProcessor";

    public static final String COMMON_ANNOTATION_PROCESSOR_BEAN_NAME =
		"org.springframework.context.annotation.internalCommonAnnotationProcessor";

    public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
                            BeanDefinitionRegistry registry, @Nullable Object source) {

        DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
        if(beanFactory ! =null) {
            if(! (beanFactory.getDependencyComparator()instanceof AnnotationAwareOrderComparator)) {
                    beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
            }
            if(! (beanFactory.getAutowireCandidateResolver()instanceof ContextAnnotationAutowireCandidateResolver)) {
                    beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
            }
        }

        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);


        / / register a ConfigurationClassPostProcessor, support @ Configuration related annotation
        / / is ConfigurationClassPostProcessor packing into BeanDefinition and register into the container
        / / [3]
        if(! registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        / / register a AutowiredAnnotationBeanPostProcessor, used to handle @ Autowire, @ Value of annotation
        if(! registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
        // Here are the @resource, @postConstruct, @predestroy annotations supporting the JSR-250 specification
        if(jsr250Present && ! registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        // Here is the BeanPostProcessor of JPA that supports annotation form
        if(jpaPresent && ! registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition();
            try {
                    def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                                    AnnotationConfigUtils.class.getClassLoader()));
            }
            catch (ClassNotFoundException ex) {
                    throw new IllegalStateException(
                                    "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
            }
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        // Support the processor for spring-event related annotations, support for @eventListener
        if(! registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(EventListenerMethodProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
        }

        // Support the processor for spring-event related annotations, support for @eventListener
        if(! registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(DefaultEventListenerFactory.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
        }

        returnbeanDefs; }}Copy the code

In the source above, registered in a lot of PostProcessor registerAnnotationConfigProcessors method, are associated with annotations processor, as this article said @ the Configuration, And @autowire, @Value, @Resource annotations. Through the source code, can be found @ Autowire and @ the underlying implementation of the Resource based on AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor respectively. Break here: registerAnnotationConfigProcessors (..) Is in AnnotationConfigApplicationContext constructor calls, specific link below call order (from above) :

// Test the main class
public class ApplicationSimpleConfiguration {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println(context.getBean("person")); }}public AnnotationConfigApplicationContext(Class
       ... componentClasses) {
    // This is registered with the PostProcessor above
    this(a); register(componentClasses); refresh(); }/ / this () to enter
public AnnotationConfigApplicationContext(a) {
    / / as previously speculated that, based on the annotation method using AnnotatedBeanDefinitionReader
    XmlBeanDefinitionReader is used for XML-based injection
    this.reader = new AnnotatedBeanDefinitionReader(this);

    // Register the default filter AnnotationTypeFilter into the includeFilter collection,
    // Filters specify annotations that we care about mainly Component, including Named and so on
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

// Enter through the constructor again
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);

    // Several postProcessors related to annotations are registered here
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
Copy the code

Continue to look at the AnnotationConfigUtils posted above the source of a class, note posted on the source of [3] place the code above, this is this mentioned ConfigurationClassPostProcessor, if block in [3], Creates a BeanDefinition class type, which is a ConfigurationClassPostProcessor, and registers to the factory container, So in the beanFactory. GetBeanNamesForType (BeanDefinitionRegistryPostProcessor. Class, true, false); When you get it, you can get the enhancer.

At this point, PostProcessorRegistrationDelegate. InvokeBeanFactoryPostProcessors (..) The processing at [1] in the code is done. Let’s talk about the code at [2]. Here’s the main point:

/ / [2]
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);     
Copy the code

Enter the calling logic, again in the current class:

# PostProcessorRegistrationDelegate.java

private static void invokeBeanDefinitionRegistryPostProcessors( Collection
        postProcessors, BeanDefinitionRegistry registry) {
    for(BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}Copy the code

The postProcessors here only contains the above ConfigurationClassPostProcessor an object, Focus on ConfigurationClassPostProcessor this class postProcessBeanDefinitionRegistry (registry) methods:

# ConfigurationClassPostProcessor.java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // The identifier is used to identify and prevent repeated processing, not important
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                            "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                            "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);

    // Handle @configure here
    processConfigBeanDefinitions(registry);
}
Copy the code

The core is the last line of code, which continues to trace:

# ConfigurationClassPostProcessor.java

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // Used to store unprocessed configuration classes
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    
    // There are six elements in this array, which are AnnotationConfigUtils support classes and MyConfig configuration class
    String[] candidateNames = registry.getBeanDefinitionNames();
    
    // Select all configuration classes from the candidates. If they are configuration classes, add them to the configuration class candidate set configCandidates
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        // If it has been processed, print a line of log; otherwise, check whether it is a configuration class
        if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! =null) {
           // Prints logs, omitted
        }
        / /!!!!!! Determine if the current beanDefinition is a configuration class, if so, collect it, and set the beanDef property value
        The // check method is explained separately below
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            If it is a configuration class that needs to be handled, add it to the list
            configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// If the candidate set of configuration class is empty, no configuration class is found
    if (configCandidates.isEmpty()) {
        return;
    }

    // If there is a sort, the configuration classes are sorted in the Order of the @order annotation
    configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
        return Integer.compare(i1, i2);
    });

    // Initialize some utility classes, environment variables, etc
    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {
        sbr = (SingletonBeanRegistry) registry;
        if (!this.localBeanNameGeneratorSet) {
            // Name the generator, not important
            BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
            if(generator ! =null) {
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator; }}}if (this.environment == null) {
        this.environment = new StandardEnvironment();
    }

    // Prepare to parse all the configuration classes found above
    
    // Create a ConfigurationClassParser instance and delegate it to parse the configuration classes
    ConfigurationClassParser parser = new ConfigurationClassParser(
                    this.metadataReaderFactory, this.problemReporter, this.environment,
                    this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    // Convert type from ArrayList -- > Set, which can be de-duplicated
    // There is really only one configuration class, the custom MyConfig class
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    // Parses are stored in this collection
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    
    // This is a loop. In fact, there are loops and recursion involved, and I'll give you a flow chart in case you get confused.
    do {
        / /!!!!!! Delegate these configuration classes to Parser!!
        // the annotation information on the ConfigurationClass is encapsulated as a ConfigurationClass object
        // If it is a normal class (not a configuration class) for a configuration class import (Import /componentScan, etc.), beanDefinition will be generated here and registered
        parser.parse(candidates);

        // Check beanDefinitionu for valid views
        // 1. Whether the configuration class can be overridden if proxyBeanMethods=true (non-final, need to generate cglib proxy class)
        // whether the method modified by 2.@Bean can be overridden (non-final, cglib proxy class generated)
        parser.validate();

        // Get all parsed configuration classes
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        Before registering BeanDefinition, remove the objects already processed in configClasses
        configClasses.removeAll(alreadyParsed);

        / / initialize a ConfigurationClassBeanDefinitionReader
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                            registry, this.sourceExtractor, this.resourceLoader, this.environment,
            this.importBeanNameGenerator, parser.getImportRegistry());
        }

        / /!!!!!! Delegate the encapsulated ConfigurationClass object to BeanDefinitionReader!!
        // Register beanDefinition by configuring class loading, as described in previous articles but not here
        this.reader.loadBeanDefinitions(configClasses);

        // add it to the alreadyParsed collection
        alreadyParsed.addAll(configClasses);

        candidates.clear();

        // After handling ConfigurationClass, new configuration classes may be registered. This is where they are collected
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                // Only newly registered BeanDefinitions need to be processed
                if(! oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName);// Is a configuration class and has not been processed
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; }}// Loop until there are no new configuration classes
    while(! candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if(sbr ! =null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
        // for a shared cache since it'll be cleared by the ApplicationContext.
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); }}Copy the code

The above code is relatively long, detailed instructions, are written in the source code, the following simple summary of the method:

  • Get all candidate classes from the container
  • From the candidate class, all configuration classes are detected
  • Iterating through all the filtered configuration classes, parsing, validating, and registering BeanDefinitions into the container
  • Handles new configuration classes that may be introduced during execution

So far, all of the above code can be briefly summarized in a flowchart:

There are two other methods that need to be highlighted, the two methods that correspond to the red background in the image. Look at the first method checkConfigurationClassCandidate ()

public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
		
    String className = beanDef.getBeanClassName();
    
    / / [4]
    // If you specify factoryMethodName, it does not count as a configuration class, as explained below
    if (className == null|| beanDef.getFactoryMethodName() ! =null) {
        return false;
    }

    AnnotationMetadata gets information for all annotations on a class

    // Finally, the annotation information on the corresponding beanDef class is stored in this metadata
    AnnotationMetadata metadata;

    // Initialize metadata into different branches depending on the BeanDefinition type
    // Here MyConfig goes to the if branch
    if (beanDef instanceof AnnotatedBeanDefinition &&
                className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
        // Can reuse the pre-parsed metadata from the given BeanDefinition...
        / / reuse
        metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
    }
    else if (beanDef instanceofAbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { Class<? > beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();// a.isassignableFrom (b.class), that is, whether A is the parent of B or the same type
        / / such as the Object. The class. IsAssignableFrom (XXX. GetClass ()) are true, because the Object is the parent class of all classes
        // If it is a derived class of these four classes, it does not count as a configuration class
        if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
                BeanPostProcessor.class.isAssignableFrom(beanClass) ||
                AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
                EventListenerFactory.class.isAssignableFrom(beanClass)) {
                return false;
        }
        metadata = AnnotationMetadata.introspect(beanClass);
    }
    else {
        try {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
            metadata = metadataReader.getAnnotationMetadata();
        }
        catch (IOException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Could not find class file for introspecting configuration annotations: " +
                                    className, ex);
            }
            return false; }}// Get the attributes of @Configuration annotation on the corresponding beanDef class: value and proxyBeanMethods
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());

    // proxyBeanMethods=true with @Configuration annotation (default)
    // proxyBeanMethods is the @Configuration attribute, which defaults to true, indicating the use of methods decorated with @beans by the CGlib proxy. Otherwise, Spring will not proxy configure instances returned by @bean modified methods inside the class

    / / [5]
    // The difference between if and else is described above:
    // Full: If @configuration is added, the BeanDefinition is full;
    // Lite: Lite with @bean,@Component,@ComponentScan, @import, @importResource;
    // Others are non-configuration classes
    // The value of this attribute can be interpreted as a flag bit. Before determining whether it is a flag class, ensure that the attribute has no value. Otherwise, it is a processed configuration class
    if(config ! =null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
        // Notice that there is a CONFIGURATION_CLASS_ATTRIBUTE tucked into the beanDefinition
        // beanDefinition is a configuration class by checking whether it has an attribute
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
    // Have @configuration or isConfigurationCandidate(metadata)
    else if(config ! =null || isConfigurationCandidate(metadata)) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else {
        return false;
    }

    // It's a full or lite configuration candidate... Let's determine the order value, if any.
    // Get the sort value
    Integer order = getOrder(metadata);
    if(order ! =null) {
        beanDef.setAttribute(ORDER_ATTRIBUTE, order);
    }

    return true;
}
Copy the code

The above codes are marked with [4] and [5] codes, which are analyzed in detail below:

[4] : Here are some ways to instantiate objects in Spring:

  • Create objects through bean tags directly in the XML configuration file;
  • Create objects with annotations such as @bean, @Service, etc.
  • Create objects from the FactoryBean factory class
  • Create an object with the factory-method attribute

Refer to Spring’s official documentation for the latter two uses, and click on the link to see how Spring instantiates objects through factory-method

[5] proxyBeanMethods are specifically illustrated at the end of the paper, which will not be written here. Briefly summarize the code at [5] :

  • If it is decorated with @Configuration and the value of the attribute proxyBeanMethods is true, then the attribute flag is set to full, that is, the full Configuration class
  • If it is decorated with @Configuration or matches the isConfigurationCandidate filter, set the attribute flag to Lite, which is the lite Configuration class described earlier
  • If no, the class is not a configuration class

There is a method isConfigurationCandidate() at [5] to determine if it is a compact configuration class:

abstract class ConfigurationClassUtils {
    // Full and compact configuration class property values
    public static final String CONFIGURATION_CLASS_FULL = "full";
    public static final String CONFIGURATION_CLASS_LITE = "lite";
    
    // This collection stores four annotations that can be used as thin provisioning classes
    If any of the following annotations are added to a class, that class is also a configuration class
    private static final Set<String> candidateIndicators = new HashSet<>(8);

    static {
            candidateIndicators.add(Component.class.getName());
            candidateIndicators.add(ComponentScan.class.getName());
            candidateIndicators.add(Import.class.getName());
            candidateIndicators.add(ImportResource.class.getName());
    }


    public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {

        // If it is an interface or annotation, it does not count as a configuration class and returns false
        if (metadata.isInterface()) {
            return false;
        }
        // Iterate over the four annotations that match those initialized in the static block above
        for (String indicator : candidateIndicators) {
            if (metadata.isAnnotated(indicator)) {
                return true; }}try {
            // If the class does not contain the above four annotations, then verify that the class contains @bean annotated methods.
            // If yes, it is a configuration class and returns true
            return metadata.hasAnnotatedMethods(Bean.class.getName());
        }
        catch (Throwable ex) {
            // Prints logs, omitted
            return false; }}}Copy the code

This flow chart of checkConfigurationClassCandidate () method already very detailed explanation, we also have found all the configuration class defined, the next step will traverse the parse, the parse method in the corresponding figure of. The source code is as follows:

# ConfigurationClassParser.java

/** * parse configuration classes!! A beanDefinition is generated and registered in a ConfigurationClass object if it is a normal class (not a ConfigurationClass) that imports (import/componentScan)@paramConfigCandidates Set of configuration classes to be parsed */
public void parse(Set<BeanDefinitionHolder> configCandidates) {
    // Iterate through all configuration class definition information
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            / / at this time of the actual type of bd AnnotatedGenericBeanDefinition, is the realization of the AnnotatedBeanDefinition subclasses
            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); }}// Delay processing is not described in this section
    this.deferredImportSelectorHandler.process();
}
Copy the code

There are three branches above, which go into different branches depending on the BeanDefinition of the configuration class. In this case, the first if block is actually entered. Continue tracing:

# ConfigurationClassParser.java

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    // The last parameter is of type Predicate
      
        filter. Predicate
       
         filter
       
      
    // The argument is constant DEFAULT_EXCLUSION_FILTER, specifying java.lang.annotation
    / / or org. Springframework. Stereotype at the beginning of class need to filter
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
Copy the code

Then follow in:

# ConfigurationClassParser.java

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {

    // check @condition to see if it needs to be skipped
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }

    ConfigurationClasses is a LinkedHashMap structure
    // The class name of the configuration class is used to obtain the encapsulation information of the configuration class
    / / parsed
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    if(existingClass ! =null) {
        // There are two cases
        if (configClass.isImported()) {
            if (existingClass.isImported()) {
                // If it has been parsed, some processing will be done
                existingClass.mergeImportedBy(configClass);
            }
            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);
            this.knownSuperclasses.values().removeIf(configClass::equals); }}// Wrap configClass as a SourceClass,
    DEFAULT_EXCLUSION_FILTER This filter defaults to DEFAULT_EXCLUSION_FILTER, which means that classes in the two packets are excluded when they are parsed
    // Mentioned earlier in the Predicate
      
    SourceClass sourceClass = asSourceClass(configClass, filter);

    do {
        // Handle the logic of the configuration class. The information in the configuration class will be encapsulated in configClass
        // Return sourceClass to override the previous sourceClass. The difference is that the new sourceClass is the parent of the old sourceClass.
        // The loop does not end until the topmost parent of the current configuration class
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    } while(sourceClass ! =null);

    // Add the completed configuration classes to the Map
    this.configurationClasses.put(configClass, configClass);
}
Copy the code

To summarize the process associated with parse(), see the following figure:

Look at doProcessConfigurationClass this method, the source code is as follows:

# ConfigurationClassParser.java

protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate
       
         filter)
       
            throws IOException {

    / / [6]
    // Note that the @configuration annotation actually declares @Component, so the if goes in
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // If the configuration class is decorated with @Component, deal with the inner class first
        processMemberClasses(configClass, sourceClass, filter);
    }

    // Process any @PropertySource annotations
    // This is the @propertysources annotation on the configuration class
    // Simply load the contents of the properties file into the memory Environment
    // We don't look closely
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                    sourceClass.getMetadata(), PropertySources.class,
                    org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        else {
           // Print logs.}}// Start with the logic for handling @ComponentScan annotations on the class
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                                                        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if(! componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {

        @componentScan - JDK >=java8
        for (AnnotationAttributes componentScan : componentScans) {
            // Delegate to componentScanParser, which returns a batch of registered BeanDefinitions
            // The parse logic here is to create a scanner and scan
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                            this.componentScanParser
                                .parse(componentScan, sourceClass.getMetadata().getClassName());
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    // If the scanned class is a configuration class, parse the logic of the configuration class
                    // Is actually a recursionparse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// Handle the @import annotation on the class
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    / / @ ImportResource processing
    // Import the spring XML configuration file
    AnnotationAttributes importResource =
                    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if(importResource ! =null) {
        String[] resources = importResource.getStringArray("locations");

        / / this value is the default BeanDefinitionReader. Class
        Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
        for (String resource : resources) {
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);

            // Encapsulate the information on the @importResource annotation into configClassconfigClass.addImportedResource(resolvedResource, readerClass); }}/ / [7]
    // Collect all @bean annotated methods in the current class
    Spring uses the ASM bytecode technology to determine the order in which methods are declared in a class
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        // encapsulate as BeanMethod and also put into configClass
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }



    // Non-abstract methods decorated with @beans will be found on all interfaces, encapsulated as BeanMethods and put into configClass
    // Since java8 interfaces can also have default methods (default-modified methods), this is also handled here
    processInterfaces(configClass, sourceClass);


    // Process superclass, if any
    SourceClass / / if the incoming parameters, have the parent class, returns the parent class sourceClass, then continue to call in the outer loop doProcessConfigurationClass parse () method
    // Encapsulates parsed information into the current configClass
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if(superclass ! =null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // Superclass found, return its annotation metadata and recurse
            returnsourceClass.getSuperClass(); }}// There is no parent class
    return null;
}
Copy the code

Above a large section of code, the source code has given a detailed description, and then summarize:

  • Handles the @Component internally, corresponding to the processMemberClasses() method, described separately below
  • Processing @ PropertySource
  • Processing @ ComponentScan
  • Processing @ Import
  • Processing @ ImportResource
  • Methods to handle @bean decorations
  • Handles default implementation methods in java8 interfaces
  • Find the incoming parameters sourceClass parent and returned to the doProcessConfigurationClass () method

We’ll give doProcessConfigurationClass () method of processing flow chart in a cycle:

ProcessMemberClasses () and @bean correspond to the [6] and [7] annotations in the source code, respectively. The other annotations are simpler and can be discussed later if you have time.

The processMemberClasses() method involves a lot of recursion because @import and other annotations introduce new classes in the process:

# ConfigurationClassParser.java

/** * Register member (nested) classes that happen to be configuration classes themselves@ComponentAnnotations, is actually get to the configuration class, all of the inner class and parsing the configuration class, namely call processConfigurationClass method * /
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate
       
         filter)
        throws IOException {
    // Get all inner classes
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
    if(! memberClasses.isEmpty()) { List<SourceClass> candidates =new ArrayList<>(memberClasses.size());
        
        // Loop through each inner class
        for (SourceClass memberClass : memberClasses) {
            If the inner class is also a configuration class, and the inner class is different from the current class
            // Is added to the candidate set of configuration classes to be processed
            if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
                        !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
                candidates.add(memberClass);
            }
        }

        // Sort the configuration classes for processing
        OrderComparator.sort(candidates);
        for (SourceClass candidate : candidates) {
            if (this.importStack.contains(configClass)) {
                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
            }
            else {
                this.importStack.push(configClass);
                try {
                    // Each inner class needs to be handled as well. This is actually going back to the top, which is a recursion
                    / / it can be said that the processing configuration @ Component annotation on the class, in fact is access to the configuration class, all of the inner class and parsing the configuration class, namely call processConfigurationClass method.
                    processConfigurationClass(candidate.asConfigClass(configClass), filter);
                }
                finally {
                    this.importStack.pop();
                }
            }
        }
    }
}
Copy the code

To summarize the above code:

  • Get all inner classes
  • If it is also a configuration class, add it to the configuration class candidate list
  • Sort the candidate configuration classes
  • Traverse, and call the processConfigurationClass parse () method. This is the same method described earlier in this article, so it’s really recursive again.

To illustrate the collection and processing logic of the @bean modification method at [7], the code is as follows:

# ConfigurationClassParser.java

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
    // Get all methods decorated by @bean
    AnnotationMetadata original = sourceClass.getMetadata();
    Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
    if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
        // Try reading the class file via ASM for deterministic declaration order...
        // Unfortunately, the JVM's standard reflection returns methods in arbitrary
        // order, even between different runs of the same application on the same JVM.
        Since the order of the list of methods returned by the JVM is not guaranteed, an attempt is made to use asm bytecode technology to get the order in which methods are declared in the class to sort the @bean modified methods
        try {
            AnnotationMetadata asm =
                            this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
            Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());

            / / sorting
            if (asmMethods.size() >= beanMethods.size()) {
                Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
                for (MethodMetadata asmMethod : asmMethods) {
                    for (MethodMetadata beanMethod : beanMethods) {
                        if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
                            selectedMethods.add(beanMethod);
                            break; }}}if (selectedMethods.size() == beanMethods.size()) {
                    // All reflection-detected methods found in ASM method set -> proceedbeanMethods = selectedMethods; }}}catch (IOException ex) {
           // Throw an exception, omit...}}return beanMethods;
}
Copy the code

The method returns a collection of methods decorated by the @bean in the configuration class.

conclusion

The collection of configuration classes and the process of registering their BeanDefinition information with the bean factory have been described in detail above, as shown below:

This article focuses on the collection of Configuration classes associated with @Configuration, as well as the source code for its dynamic proxy. This article will not cover AOP related content for the time being. Here, the entrance is in PostProcessorRegistrationDelegate type invokeBeanFactoryPostProcessors () method, Concrete is in processing BeanDefinitionRegistryPostProcessor type of call, will call enhanceConfigurationClasses (the beanFactory) method, the code is as follows:

public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List
       
         beanFactoryPostProcessors)
        {

    Set<String> processedBeans = new HashSet<>();

    if (beanFactory instanceof BeanDefinitionRegistry) {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

        List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
        List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

        for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
            if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;
                registryProcessor.postProcessBeanDefinitionRegistry(registry);
                registryProcessors.add(registryProcessor);
            }
            else {
                regularPostProcessors.add(postProcessor);
            }
        }

        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

        // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);

        for (String ppName : postProcessorNames) {
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }

        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);

        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

        currentRegistryProcessors.clear();

        // Next, invoke the BeanDefinitionRegistryPostProcessors that implement 
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);

        for (String ppName : postProcessorNames) {
            if(! processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear();// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
        boolean reiterate = true;
        while (reiterate) {
            reiterate = false;
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);
            for (String ppName : postProcessorNames) {
                if(! processedBeans.contains(ppName)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); reiterate =true;
                }
            }
            registryProcessors.addAll(currentRegistryProcessors);
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
        }
        // Now, invoke the postProcessBeanFactory callback of all processors handled so far.
        / / here will actually call enhanceConfigurationClasses () method, additional agent processing, this paper, the follow-up to the AOP, then to discuss the content
        invoeBeanFactoryPostProcessors(registryProcessors, beanFactory);
        invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
    }
    else {
        / /...
    }
    
    / /...
}
Copy the code

At the end of the article

The proxyBeanMethods attribute of the @Configuration annotation was mentioned earlier. Here are some changes based on the previous debugging code

Add a new object class: car.java

public class Car {}Copy the code

Modify the Constructor of Person

public class Person {
    public Person(Car car) {
        System.out.println("Inside:"+ car); }}Copy the code

The configuration class is modified as follows: myconfig.java

@Configuration
public class MyConfig {
    @Bean
    public Person person(a) {
        // The @bean method calls another @bean method
        return new Person(car());
    }
    @Bean
    public Car car(a) {
        return newCar(); }}Copy the code

The test class:

public class ApplicationSimpleConfiguration {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);

        System.out.println("car = " + context.getBean("car"));
        System.out.println("person = " + context.getBean("person")); }}Copy the code

Look at the print: You can see that the two Cars are consistent

But if you modify the Configuration class: Remove the @Configuration annotation or set its property proxyBeanMethods = false

@Configuration
public class MyConfig {
    @Bean
    public Person person(a) {
        // The @bean method calls another @bean method
        return new Person(car());
    }
    @Bean
    public Car car(a) {
        return newCar(); }}Copy the code

Observations: