Spring 5 Source Resolution 6-Configuration ClassParser Resolution Configuration Class

Keywords: Java Spring xml github

ConfigurationClassParser

In the Configuration ClassPostProcessor#processConfigBeanDefinitions method, the Configuration ClassParser object is created and its parse method is called. This method is responsible for parsing configuration classes, scanning packages, and registering BeanDefinition. The source code is as follows:

//Configuration ClassParser#parseSet <Bean Definition Holder> Method Source
public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // Call different parse methods based on different BeanDefinition instance objects
            // The bottom layer is actually calling org. spring framework. context. annotation. Configuration ClassParser. process Configuration Class
            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);
        }
    }

    //Execute Deferred Import Selector
    this.deferredImportSelectorHandler.process();
}

Within this method, different parse methods are invoked according to different BeanDefinition instance objects, and the bottom layer of these parse methods is actually the Configuration ClassParser# processConfiguration Class method.

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    // Need to skip @Conditional
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }

    // At first entry, configurationClasses size = 0,existingClass must be null
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    if (existingClass != null) {
        if (configClass.isImported()) {
            if (existingClass.isImported()) {
                existingClass.mergeImportedBy(configClass);
            }
            // Otherwise ignore new imported config class; existing non-imported class overrides it.
            return;
        } else {
            // Explicit bean definition found, probably replacing an import.
            // Let's remove the old one and go with the new one.
            this.configurationClasses.remove(configClass);
            this.knownSuperclasses.values().removeIf(configClass::equals);
        }
    }

    // Recursively process the configuration class and its superclass hierarchy.
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        // Real analysis
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);

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

The ConfigurationClass object passed in by the method is the encapsulation of the configuration class. First, determine whether there is an @Conditional annotation on the configuration class and whether it needs to skip parsing the configuration class.

Then, call doProcessConfiguration Class (configClass, sourceClass); do real parsing. Among them, configClass is the configuration class of the program, and sourceClass is created by configClass.

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

    // @ Configuration inherits @Component
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // Recursively process any member (nested) classes first
        // Recursive processing of internal classes
        processMemberClasses(configClass, sourceClass);
    }

    // Process any @PropertySource annotations
    // Processing @PropertySource
    // @ PropertySource annotations are used to load properties files
    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");
        }
    }

    // Process any @ComponentScan annotations
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // Check the set of scanned definitions for any further config classes and parse recursively if needed
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                //Determine if there are configuration classes in the parsed retrieved BeanDefinition
                // The configuration classes here include Full Configuration Class and LiteConfiguration Class
                // That is, as long as you have one of the annotations in @Configuration, @Component, @ComponentScan, @Import, @ImportResource and @Bean.
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    //If you have a configuration class, call it recursively, parse the configuration class, the if is almost true, and the method is almost always executed.
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // Process any @ImportResource annotations
    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);
        }
    }

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

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

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

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

Resolve internal classes

There is a @Configuration annotation on the configuration class, which inherits @Component, if judged to be true, calls the processMemberClasses method, and recursively parses the internal classes in the configuration class.

Resolve the @PropertySource annotation

If there is a @PropertySource annotation on the configuration class, the properties file is parsed and loaded, and the properties are added to the Spring context. ((Configurable Environment) this. environment). getPropertySources (). addFirstPropertySource (new Source);.

Processing @ComponentScan annotations

Get the @ComponentScan annotation on the configuration class to determine if skipping is necessary. Loop all ComponentScans and perform the scan immediately. The ComponentScan Annotation Parser parse method is as follows:

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    // Create ClassPathBean Definition Scanner
    // A ClassPathBean Definition Scanner is also created in the Annotation Config Application Context constructor
    // This proves that the scanner execution is not in the constructor, but created here.
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

    // @ Custom BeanName Generator can be registered in ComponentScan
    // Note, however, that as you can see from the source code, the custom BeanName Generator registered here is only valid for the current scanner
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
            BeanUtils.instantiateClass(generatorClass));

    ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
        scanner.setScopedProxyMode(scopedProxyMode);
    } else {
        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    }

    scanner.setResourcePattern(componentScan.getString("resourcePattern"));

    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addIncludeFilter(typeFilter);
        }
    }
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addExcludeFilter(typeFilter);
        }
    }

    boolean lazyInit = componentScan.getBoolean("lazyInit");
    if (lazyInit) {
        scanner.getBeanDefinitionDefaults().setLazyInit(true);
    }

    Set<String> basePackages = new LinkedHashSet<>();
    String[] basePackagesArray = componentScan.getStringArray("basePackages");
    for (String pkg : basePackagesArray) {
        String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        Collections.addAll(basePackages, tokenized);
    }

    // @ComponentScan(basePackageClasses = Xx.class)
    // basePackageClasses can be specified and can be scanned by Spring as long as they are packages and their subpackages that belong to these classes.
    // Often an empty class is used as the base Package Classes, defaulting to the package and its subpackages where the current configuration class is located.
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    if (basePackages.isEmpty()) {
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }

    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
        @Override
        protected boolean matchClassName(String className) {
            return declaringClass.equals(className);
        }
    });

    //Executive scan
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}

Pick out some places that I think are the key points to record:

  1. A new ClassPathBeanDefinitionScanner object is created in the parse method, and a ClassPathBeanDefinitionScanner object is created in the AnnotationConfigApplicationContext constructor, which confirms that within Spring, it is not the scanner in AnnotationConfigApplicationContext that actually performs the scan.
  2. As you can see from the source code, a custom BeanName Generator can be registered in @ComponentScan, and this BeanName Generator is only valid for the current scanner. That is to say, the BeanName Generator can only affect the BeanName generation rules of bean s under the scanner scanning path.
  3. Finally, call scanner. doScan (StringUtils. toStringArray (base Packages); the method performs a real scan, and the method returns the BeanDefinition obtained by the scan.

Verify that there are configuration classes in the obtained BeanDefinition

Check if there are configuration classes in the BeanDefinition scanned, and if there are configuration classes, the configuration classes here include Full Configuration Class and LiteConfiguration Class. (That is to say, as long as there is one annotation in @Configuration, @Component, @ComponentScan, @Import, @ImportResource and @Bean), the parse method is called recursively for parsing.

Resolve the @Import annotation

processImports(configClass, sourceClass, getImports(sourceClass), true);

The processImports method is responsible for parsing the @Import annotation. Configuration Class is a configuration class, and sourceClass is created by configClass. getImports(sourceClass) gets all @Import annotation information from sourceClass, and then calls Configuration ClassParser process Imports.

// Configuration ClassParser processImports source code
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

    if (importCandidates.isEmpty()) {
        return;
    }

    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    } else {
        this.importStack.push(configClass);
        try {
            // Import Candidates is the encapsulation of @Import
            // Loop import Candidates to classify import content
            for (SourceClass candidate : importCandidates) {
                // import Import ImportSelector Interface Classes
                if (candidate.isAssignable(ImportSelector.class)) {
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    Class<?> candidateClass = candidate.loadClass();
                    // Reflection creates instance objects of this class
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    //Is there an implementation of the relevant Aware interface, and if so, this calls the relevant method
                    ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);
                    // ImportSelector for Delayed Loading
                    if (selector instanceof DeferredImportSelector) {
                        //  ImportSelector with delayed loading is first placed in List, and delayed loading
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    } else {
                        // Ordinary ImportSelector, which executes its selectImports method, obtains a fully qualified array of class names for the classes that need to be imported
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                        // Recursive call
                        processImports(configClass, currentSourceClass, importSourceClasses, false);
                    }
                    // Is it ImportBean Definition Registrar
                } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // Candidate class is an ImportBeanDefinitionRegistrar ->
                    // delegate to it to register additional bean definitions
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar =
                            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            registrar, this.environment, this.resourceLoader, this.registry);
                    // Add to the member variable org. spring framework. context. annotation. Configuration Class. importBeanDefinition Registrars
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                } else {
                    // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                    // process it as an @Configuration class
                    // Ordinary @Configuration class
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    // Resolve the imported @Configuration class
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                            configClass.getMetadata().getClassName() + "]", ex);
        } finally {
            this.importStack.pop();
        }
    }
}

Resolve the @ImportResource annotation

@ ImportResource annotations can be imported into xml configuration files.

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

Resolving the @Bean method

Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

Convert the @Bean method into a BeanMethod object and add it to the Configuration Class BeanMethods collection.

If there is a parent class, it resolves the parent class

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
        return sourceClass.getSuperClass();
    }
}

If there is a parent class, it returns the parent Class object and continues to call the method. Until null is returned, the outer loop ends.

do {
    // Real analysis
    sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);

Source Learning Notes: https://github.com/shenjianen...

Welcome to pay attention to the public number.

Posted by notaloser on Wed, 09 Oct 2019 00:59:42 -0700