preface

@configuration @component @bean @import and so on. I believe you will often use these annotations when using Spring. I wonder how these annotations are handled in Spring. Today we are going to explore the secret of the spring annotations – ConfigurationClassPostProcessor

The class diagram

Check ConfigurationClassPostProcessor class diagram, can clearly see it realized BeanDefinitionRegistryPostProcessor and PriorityOrdered respectively, if you have read my previous article, Can know this class execution time, here is the class code execution, entrusted to PostProcessorRegistrationDelegate execution,

String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);
for (String ppName : postProcessorNames) {
  / / if the class is implemented PriorityOrdered and implements BeanDefinitionRegistryPostProcessor into temporary list
  if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
    processedBeans.add(ppName);
  }
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// Execute here
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
Copy the code
// Execute code is also very simple, just a for loop
private static void invokeBeanDefinitionRegistryPostProcessors( Collection
        postProcessors, BeanDefinitionRegistry registry) {

    for(BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}Copy the code

Registration time

So the question comes, ConfigurationClassPostProcessor when is registered to the IOC container?

/ / AnnotationConfigApplicationContext constructor will initialize AnnotatedBeanDefinitionReader
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
   super(beanFactory);
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}
Copy the code
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);
    / / AnnotatedBeanDefinitionReader registered in advance support component annotation
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
Copy the code
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {
        / / get DefaultListableBeanFactory, this method is only a type conversion
        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 ConfigurationClassPostProcessor here
        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));
        }
        / / registered AutowiredAnnotationBeanPostProcessor, processing the @autowired, bean with the properties of the injection
        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.
        / / register CommonAnnotationBeanPostProcessor, with some public comments, such as @ Resource, @ PostConstruct, @ PreDestroy
        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));
        }

        // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
        // The class will be registered if the jar package related to jPA is imported
        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));
        }
        / / registered EventListenerMethodProcessor, handle @ 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));
        }

        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));
        }

        return beanDefs;
}
Copy the code

The source code parsing

postProcessBeanDefinitionRegistry

ConfigurationClassPostProcessor BeanDefinitionRegistryPostProcessor interface is achieved, then we can simply find entrance to perform:

postProcessBeanDefinitionRegistry

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        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);
        // The above are some checks, directly look at the core code
        processConfigBeanDefinitions(registry);
}
Copy the code

Before we look at the following source code, let’s take a look at the difference between the configuration class Full mode and Lite mode: Full mode configuration classes are proxied by Cglib, lite mode configuration classes are not proxied by Cglib, so @bean methods cannot be declared private and final in full mode.

Let’s take a test to see the difference

@Configuration // Use @configuration to distinguish between the full Configuration class and lite Configuration class
public class FullConfig {

    @Bean
    public String test(a) {
        System.out.println(user().hashCode());
        System.out.println(user().hashCode());
        return "test";
    }

    @Bean
    public User user(a) {
        return newUser(); }}Copy the code
@Component
public class LiteConfig {
    @Bean
    public String test(a) {
        System.out.println(user().hashCode());
        System.out.println(user().hashCode());
        return "test";
    }

    @Bean
    public User user(a) {
        return newUser(); }}Copy the code
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext(LiteConfig.class); context.getBean(User.class); context.close(); }}Copy the code

Information printed in Lite mode

Information printed in full mode

Summary: In Lite mode, multiple calls to User () generate multiple objects, but not in Full mode

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // To save the full configuration class
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // Retrieve all names registered with beanDefinition in the current Registry
    String[] candidateNames = registry.getBeanDefinitionNames();

    
    for (String beanName : candidateNames) {
                BeanDefinition beanDef = registry.getBeanDefinition(beanName);
                // org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass
                // BeanDefinition is not empty, which means it has been set before
                if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! =null) {
                        if (logger.isDebugEnabled()) {
                                logger.debug("Bean definition has already been processed as a configuration class: "+ beanDef); }}// Check whether it is a configuration class and set the BeanDefinition property to Lite or full
                // Set the lite and full property values for BeanDefinition here for later use
                // See below for a detailed explanation of this method
                else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                        configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// No Configuration class to be processed (@Configuration) is found
        if (configCandidates.isEmpty()) {
                return;
        }

        // Sort by @order
        configCandidates.sort((bd1, bd2) -> {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return Integer.compare(i1, i2);
        });


        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
                sbr = (SingletonBeanRegistry) registry;
                // Check whether a naming policy is set
                if (!this.localBeanNameGeneratorSet) {
                        BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                        if(generator ! =null) {
                              // Set the naming policy
                                this.componentScanBeanNameGenerator = generator;
                                this.importBeanNameGenerator = generator; }}}// Set the environment variable, which was initialized earlier
        if (this.environment == null) {
                this.environment = new StandardEnvironment();
        }

        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                        this.metadataReaderFactory, this.problemReporter, this.environment,
                        this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        // Store all configuration classes
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        // Store the parsed configuration classes
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        // Note: here is a big do... While loop, which exits after all configuration classes have been parsed
        do {
        // Delegate to ConfigurationClassParser to resolve these configuration classes
                parser.parse(candidates);
                // Check whether the scanned beanDefinition is valid
                // 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();

                Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
                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
                // Resolve ConfigurationClass to BeanDefinition
                this.reader.loadBeanDefinitions(configClasses);
                alreadyParsed.addAll(configClasses);

                candidates.clear();
                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) {
                                if(! oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName);// The parsed BeanDefinition is checked again to see if it is a Full or lite configuration class
                                        // For example, a class imports a configuration class
                                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; }}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
Determine a fully configured class or a semi-configured class
public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

  	String className = beanDef.getBeanClassName();
        if (className == null|| beanDef.getFactoryMethodName() ! =null) {
                return false;
        }

        AnnotationMetadata metadata;
        // Classes that are not pre-registered with Spring will do this if
        if (beanDef instanceof AnnotatedBeanDefinition &&
                        className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
                // Can reuse the pre-parsed metadata from the given BeanDefinition...
                metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        }
        // Return false filters spring pre-registered classes
        else if (beanDef instanceofAbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { Class<? > beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();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 {
                        // the ASM technology gets information about this class
                        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;
                }
        }

        Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        // There are two judgments here
        // 1. The current class has the @Configuration annotation
        // 2. In annotations, the proxyBeanMethods attribute is true
        // Set it to the full configuration class
        if(config ! =null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
                beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        // Classes with @Configuration and true for proxyBeanMethods are evaluated
        // There are two kinds of logic
        // 1. Contains @configuration and proxyBeanMethods is false
        // 2. Contains @bean, @Component, @ComponentScan, @import, @importResource annotations
        // The above two types are set to Lite configuration classes
        else if(config ! =null || isConfigurationCandidate(metadata)) {
                beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
                return false;
        }

        The Lite or full configuration class sets the order priority
        Integer order = getOrder(metadata);
        if(order ! =null) {
                beanDef.setAttribute(ORDER_ATTRIBUTE, order);
        }

        return true;
}
Copy the code
// candidateIndicators Static constant contains four elements when initialized
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) {
        // This class is an interface that returns false directly
        if (metadata.isInterface()) {
                return false;
        }

        // candidateIndicators is a static constant that, when initialized, contains four elements
        // are @bean, @Component, @ComponentScan, @import, @importResource respectively
        // The class is a configuration class as long as one of the four annotations is added to it
        // The configurationClass attribute in BeanDefinition is Lite
        for (String indicator : candidateIndicators) {
                if (metadata.isAnnotated(indicator)) {
                        return true; }}// Look for methods annotated with @bean
        try {
                return metadata.hasAnnotatedMethods(Bean.class.getName());
        }
        catch (Throwable ex) {
                if (logger.isDebugEnabled()) {
                        logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]." + ex);
                }
                return false; }}Copy the code
ConfigurationClass

What are the annotations in a configuration class, and how are they described? Spring uses ConfigurationClass to abstract a ConfigurationClass

final class ConfigurationClass {
	// Configure the annotation information for the class
	private final AnnotationMetadata metadata;

	private final Resource resource;

	@Nullable
	private String beanName;
	// Which configuration class is importing the current class
	private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
	// All the @bean-annotated methods of the current configuration class
	private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
	// @importResource related information, this is ignored
	private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
			new LinkedHashMap<>();
	/ / @ Import annotations on the configuration class imported classes, if is to implement the @ ImportBeanDefinitionRegister interface, will be wrapped in here
	private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
			new LinkedHashMap<>();

	final Set<String> skippedBeanMethods = new HashSet<>();
}
Copy the code
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
                return;
        }
        // Whether the configuration class has been resolved, such as the current class and imported by other classes
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if(existingClass ! =null) {
                if (configClass.isImported()) {
                        if (existingClass.isImported()) {
                              // A configuration class is imported by multiple classes
                                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);
                        this.knownSuperclasses.values().removeIf(configClass::equals); }}// sourceClass: encapsulates the metadata of the configuration class, such as what annotations and methods does the configuration class have
        / / the configClass here just instantiate a good object, was an empty shell, doProcessConfigurationClass configuration class for parsing, and filling
        // configClass
        SourceClass sourceClass = asSourceClass(configClass, filter);
        // Do... The while loop deals with cases where a class has a parent, which continues to parse the parent
        do {
                sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
        }
        while(sourceClass ! =null);

        // Put the processed classes into configurationClasses
        this.configurationClasses.put(configClass, configClass);
}
Copy the code
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

            if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
                    // If the configuration class is decorated with @Component, deal with the inner class first
                    processMemberClasses(configClass, sourceClass);
            }

            // Handle @propertysource and @propertysources annotations
            // Converts the file to an PropertySource object, which is then stored in the Environment object
            for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                            sourceClass.getMetadata(), PropertySources.class,
                            org.springframework.context.annotation.PropertySource.class)) {
                    if (this.environment instanceof ConfigurableEnvironment) {
                            processPropertySource(propertySource);
                    }
                    else {
                            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                                            "]. Reason: Environment must implement ConfigurableEnvironment"); }}// Handle @ComponentScan and @ComponentScans 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) {
                            // Process the scan, not the configuration of the class
                            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)) {
                                            // Iterate over all the scanned classesparse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// Handle the @import annotation
            processImports(configClass, sourceClass, getImports(sourceClass), true);

            // Handle the @importResource annotation
            AnnotationAttributes importResource =
                            AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
            if(importResource ! =null) {
                    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); }}// Handle @bean annotations
            Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
            for (MethodMetadata methodMetadata : beanMethods) {
                    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
            }

            // Since Java8 supports default methods defined in interfaces, the default methods of interfaces are handled here
            processInterfaces(configClass, sourceClass);

            // If the class has a parent, return the parent with a do... The while loop will run through this method again
            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(); }}// If there is no parent class, return null
            return null;
 }
Copy the code

Above is ConfigurationClassPostProcessor roughly the execution process, but the details of each annotation processing, half a reader should or half would know solution, below added

@Component

Take a look at some code to get a sense of how this annotation is used

@Component
public class ComponentConfig {
    // An inner class
    class InnerClass {
        @Bean
        public User user(a) {
            System.out.println("component test");
            return newUser(); }}}Copy the code
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate
       
         filter)
        throws IOException {
        // Get all inner classes
        Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
        // If inner class is included
        if(! memberClasses.isEmpty()) { List<SourceClass> candidates =new ArrayList<>(memberClasses.size());
                // Loop through the inner class
                for (SourceClass memberClass : memberClasses) {
                        // There are two judgments here
                        // 1. Is the inner class a configuration class
                        // 2. The name of the inner class is inconsistent with that of the current class.
                        // Add the configuration class to the list
                        if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
                                        !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
                                candidates.add(memberClass);
                        }
                }
                // Sort the classes to be configured
                OrderComparator.sort(candidates);
                for (SourceClass candidate : candidates) {
                        // Check if there is a looped import
                        if (this.importStack.contains(configClass)) {
                                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
                        }
                        else {
                                this.importStack.push(configClass);
                                try {
                                        // Handle inner class, here is a recursion, again go through the process of parsing (see above)
                                        processConfigurationClass(candidate.asConfigClass(configClass), filter);
                                }
                                finally {
                                        this.importStack.pop();
                                }
                        }
                }
        }
}
Copy the code
@ComponentScan

Take a look at some code to get a sense of how this annotation is used

@ComponentScan(value = "com.example")
public class ComponentScanConfig {}Copy the code
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
        // Create a scanner
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

                Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
        // @ComponentScan annotations can configure naming policies (nameGenerator)
        // The default is true. If false, the naming policy uses the one you configured on the configuration class
        . / / BeanUtils instantiateClass here will be initialized to command strategy class
        boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
                scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ......return scanner.doScan(StringUtils.toStringArray(basePackages));
}
Copy the code
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
                // Scan to find the class you want to load
                Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
                for (BeanDefinition candidate : candidates) {
                        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                        candidate.setScope(scopeMetadata.getScopeName());
                        String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                        if (candidate instanceof AbstractBeanDefinition) {
                                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                        }
                        // Annotated classes will go through the if
                        if (candidate instanceof AnnotatedBeanDefinition) {
                                // Handle some common annotations, such as @lazy and @primary
                                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                        }
                        // Check whether the beanDefinition is registered. If so, it will not be registered again
                        if (checkCandidate(beanName, candidate)) {
                                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                                definitionHolder =
                                                AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                                beanDefinitions.add(definitionHolder);
                                / / register beanDefinition
                                registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
}
Copy the code
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
              // Convert the package name to a path
              // The path here refers to the path after compiling to bytecode
              Com.example --> classpath*:com/example/**/*.class
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
                Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
                boolean traceEnabled = logger.isTraceEnabled();
                boolean debugEnabled = logger.isDebugEnabled();
                for (Resource resource : resources) {
                        if (traceEnabled) {
                                logger.trace("Scanning " + resource);
                        }
                        if (resource.isReadable()) {
                                try {
                                        MetadataReader describes the class information using ASM techniques
                                        // Why use ASM to parse classes instead of class.forname reflection?
                                        // Since using ASM is a non-invasive way, if you use reflection, you need to load the class file into the JVM first.
                                        // Some of the class's initialization code (static) is executed, which is not what a framework should do
                                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                                        // Remove classes that don't need to be loaded, such as excludeFilters, @condition annotations
                                        if (isCandidateComponent(metadataReader)) {
                                                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                                                sbd.setSource(resource);
                                                if (isCandidateComponent(sbd)) {
                                                        if (debugEnabled) {
                                                                logger.debug("Identified candidate component class: " + resource);
                                                        }
                                                        candidates.add(sbd);
                                                }
                                                else {
                                                        if (debugEnabled) {
                                                                logger.debug("Ignored because not a concrete top-level class: "+ resource); }}}else {
                                                if (traceEnabled) {
                                                        logger.trace("Ignored because not matching any filter: "+ resource); }}}catch (Throwable ex) {
                                        throw new BeanDefinitionStoreException(
                                                        "Failed to read candidate component class: "+ resource, ex); }}else {
                                if (traceEnabled) {
                                        logger.trace("Ignored because not readable: "+ resource); }}}}catch (IOException ex) {
                throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
}
Copy the code
@Import

Take a look at some code to get a sense of how this annotation is used

public class ImportSelectorConfig implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String[] str = {"com.example.springdemo.bean.User"};
        System.out.println("ImportSelectorConfig");
        returnstr; }}Copy the code
public class ImportBeanDefinitionRegistrarConfig implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("ImportBeanDefinitionRegistrarConfig");
        ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry); }}Copy the code
@Data
@Builder
public class Staff {

    public Staff(a) {
        System.out.println("staff"); }}Copy the code
@Configuration
@Import({Staff.class, ImportSelectorConfig.class, ImportBeanDefinitionRegistrarConfig.class})
public class Config {}Copy the code

Summary: The import annotation can import three types of classes

  • ImportSelector type
  • ImportBeanDefinitionRegistrar type
  • Ordinary class
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {
		// import annotations do not import any classes
		if (importCandidates.isEmpty()) {
			return;
		}
		// Check to see if there is a loop import
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
                        // Import classes are handled here. There are three types of import classes
                        / / 1. ImportSelector type
                        / / 2. ImportBeanDefinitionRegistrar type
                        // 3
                        for (SourceClass candidate : importCandidates) {
                                if (candidate.isAssignable(ImportSelector.class)) {
                                        // Candidate class is an ImportSelector -> delegate to it to determine importsClass<? > candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment, this.resourceLoader, this.registry);
                                        Predicate<String> selectorFilter = selector.getExclusionFilter();
                                        if(selectorFilter ! =null) {
                                                exclusionFilter = exclusionFilter.or(selectorFilter);
                                        }
                                        if (selector instanceof DeferredImportSelector) {
                                                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                                        }
                                        else {
                                                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                                                Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                                               // There is a recursion for processImports. Why do I recurse only if the import class is an ImportSelector?
                                               / / ImportBeanDefinitionRegistrar and ordinary class can't?
                                               / / because ImportSelector might import multiple classes, these classes may have ImportSelector, ImportBeanDefinitionRegistrar
                                               // Or a normal class that needs to be processed again
                                               // The ImportSelector class itself is meaningless to Spring; its purpose is to import the class
                                              processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); }}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                                        / / import classes are ImportBeanDefinitionRegistrar type
                                        // It will be provisioned for ConfigurationClassClass<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment, this.resourceLoader, this.registry);
                                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                                }
                                else {
                                        / / import the imported class is neither ImportBeanDefinitionRegistrar, nor ImportSelector
                                        // Treat it as a configuration class, and then go through the configuration class process
                                        this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); }}}catch (BeanDefinitionStoreException ex) {
                        throw ex;
                }
                catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                        "Failed to process import candidates for configuration class [" +
                                        configClass.getMetadata().getClassName() + "]", ex);
                }
                finally {
                        this.importStack.pop(); }}}Copy the code
loadBeanDefinitions
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
		// If the class is added to the container via @import or ImportSelector, this if is followed
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
                // If the class contains @bean methods, go if
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
		// Handle the @importResource annotation
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
                / / if the class of @ Import annotations, and Import the class implements the ImportBeanDefinitionRegistrar
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
Copy the code
@Bean

Take a look at some code to get a sense of how this annotation is used

@Configuration
public class BeanConfig {
    @Bean
    public User user(a) {
        System.out.println("user");
        return new User();
    }

    @Bean
    public static Staff staff(a) {
        System.out.println("staff");
        return newStaff(); }}Copy the code

The main difference between @beans (static) and @beans (static) is that they are initialized at the same time

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {· · · · · · · · · ·// If the @bean method is static, set the beanClass to be called when the Bean is initialized
		if (metadata.isStatic()) {
			// static @Bean method
			if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
				beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
			}
			else {
				beanDef.setBeanClassName(configClass.getMetadata().getClassName());
			}
                        / / set FactoryMethodName
			beanDef.setUniqueFactoryMethodName(methodName);
		}
		else {
			// If the @bean method is non-static, set factoryBeanName to be called when the Bean is initialized
			beanDef.setFactoryBeanName(configClass.getBeanName());
                        / / set FactoryMethodNamebeanDef.setUniqueFactoryMethodName(methodName); } · · · · · · · · · ·/ / register beanDefinition
		this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
Copy the code

conclusion

This article is going to look boring, and I wanted to draw a graph to help me understand it, but the whole method has a lot of recursion, so it’s hard to draw a graph, so it’s just like this