Today, let’s talk about the @enable module driver. The Spring Framework supports the @enable module driver since version 3.1. The so-called “module” refers to a collection of functional components with the same domain, combined to form an independent unit.

The diagram is the @Enable annotation module for Spring Framework, Spring Boot, and Spring Cloud:

Framework implementations @enable annotation module Activate the module
Spring Framework @EnableWebMvc Web Mvc module
@EnableTransactionManagement Transaction management module
@EnableCaching Caching module
@EnableMBeanExport JMX module
@EnableAsync Asynchronous processing module
@EnableWebFlux Web Flux module
@EnableAspectJAutoProxy AspectJ module
Spring Boot @EnableAutoConfiguration Automatic assembly module
@EnableManagementContext Physical modules
@EnableConfigurationProperties Configure the property binding module
@EnableOAuth2Sso OAuth2 single sign-on module
spring Cloud @EnableEurekaServer Eureka service module
@EnableConfigServer Configuring the Server Module
@EnableFeignCliens Feign client module
@EnableZuulProxy Service gateway Zuul module
@EnableCircuitBreaker Service fuse module
  • The point of introducing the @enable module driver is to EnableSimplify assembly stepsTo achieve the"Assemble as needed"While masking the assembly details of the component collection.

1, analyze @enable “module driver”

This starts with the new @import feature in Spring Framework3.0. @import is used to Import one or more ConfigurationClasses and register them as Spring beans. Here are some things to note:

  • There is a limitation in Spring Framework3.0 that only classes annotated with @configuration are supported
  • In the Spring Framework3.1, @ Import, expand the scope of the accused, can also be used to declare at least one @ Bean method class, as well as the implementation class ImportSelector or ImportBeanDefinitionRegistrar

As you may have noticed, the Spring Framework provides two implementations of the @enable module driver:

  • Classes declared by the @Configuration class and the @bean method are classified asannotation-driven.
  • In order to ImportSelector or ImportBeanDefinitionRegistrar implementation classes classified as"Interface programming".

1) First, let’s take the @enableAsync annotation:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
    ...
}
Copy the code

We found that the source of the @ EnableAsync above indicate the @ Import ({AsyncConfigurationSelector. Class}). @ Import role as I’ve mentioned, opens at the next AsyncConfigurationSelector class, look at what things:

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {... }Copy the code

Well, we found that AsyncConfigurationSelector inherited AdviceModeImportSelector class, then the point we are open AdviceModeImportSelector class, look at what’s the secret:

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {... }Copy the code

Does that look familiar? Yes, AdviceModeImportSelector implements the ImportSelector class, so can we infer that @enableaysnc implements the @enbale module in an “interface programming” way?

2) So, let’s take a look at the mystery of the @enableWebMVC annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
Copy the code

Above source believe that friends have guessed, @ EnableWebMvc marking the @ Import ({DelegatingWebMvcConfiguration. Class}). What is good, we open the DelegatingWebMvcConfiguration look at the source code:

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {... }Copy the code

Do you see anything special about it? DelegatingWebMvcConfiguration class is a @ the Configuration of the class. So we can also infer that @enableWebMVC implements the @bale module in an “annotation-driven” way.

I believe that many friends can already customize the @enable module driver.

2. Driver principle of @enable module

As mentioned earlier, the @Enable module driver is implemented using @import, and it is the responsibility of @import to load the Import Class, defining it as Spring beans. Import classes mainly @ the Configuration Class, ImportSelector implementation and ImportBeanDefinitionRegistrar implementation.

1) Load @Configuration Class

  • @configuration was introduced in Spring Framework3.0, but the @componentscan annotation driver is not yet introduced, so the accompanying Import annotation is @import.
  • Although Spring Framework3.0 provides annotations device contexts achieve AnnotationConfigApplicationContext, but compared with @ Import with trival, supports only one by one into the Spring component class, If this @ Import ({A.c lass, biggest lass,… }), so it was not yet a complete replacement for XML elements<context:component-scan/>.
  • Even if the Spring application context is not<context:component-scan/>Combined, @import processing still fails. So we used to configure XML elements while developing Spring projects<context:component-scan/>with<context:annotation-config/>.

Below we analysis – config / > < context: an annotation for the realization of the BeanDefinitionParser classes AnnotationConfigBeanDefinitionParser:

public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {...@Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source); .return null; }}Copy the code

If we look at the source code, Parse (Element, ParserContext) method call AnnotationConfigUtils# registerAnnotationConfigProcessors (BeanDefinitionRegistry, Object) Method implements instance resolution of a BeanDefinition(used to store Bean definition information).

The method from Spring Framework3.0 began, added @ the Configuration Class ConfigurationClassPostProcessor implementations of processing:

public abstract class AnnotationConfigUtils {...public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {... Set<BeanDefinitionHolder> beanDefs =new LinkedHashSet(8);
        RootBeanDefinition def;
        if(! registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
            def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor")); }...returnbeanDefs; }}Copy the code

I can see from source, ConfigurationClassPostProcessor is encapsulated into a Spring Bean definitions (BeanDefinition), then registered as a Spring Bean, And the name of the Bean as “org. Springframework. Context. The annotation. InternalConfigurationAnnotationProcessor”.

So let’s imagine, so in the Spring Framework:

  • <context:annotation-config/>The underlying implementation class AnnotationConfigBeanDefinitionParser callsAnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)Methods registered ConfigurationClassPostProcessor Bean.
  • <context:component-scan/>The underlying implementation class ComponentScanBeanDefinitionParser call tooAnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)Methods registered ConfigurationClassPostProcessor Bean.

Actually ConfigurationClassPostProcessor not only in the XML configuration can only be driven by assembly, Spring Framework3.0 implemented annotation driven context AnnotationConfigApplicationContext assembly:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    private finalAnnotatedBeanDefinitionReader reader; .public AnnotationConfigApplicationContext(a) {
        this.reader = new AnnotatedBeanDefinitionReader(this); . }... }Copy the code

We continue to observe a member variable AnnotatedBeanDefinitionReader constructor:

public class AnnotatedBeanDefinitionReader {...public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, getOrCreateEnvironment(registry));
    }

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {...this.registry = registry; . AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }}Copy the code

Source analysis above, we can find AnnotationConfigApplicationContext AnnotatedBeanDefinitionReader type members reader at construction time, Also the explicit calls AnnotationConfigUtils# registerAnnotationConfigProcessors (BeanDefinitionRegistry, Object) method.

In the Spring application context start (AbstractApplicationContext# refresh () method is called), The Spring container (the BeanFactory) will initialize ConfigurationClassPostProcessor Spring Bean. As spring BeanFactoryPostProcessor implementation, then its postProcessBeanFactory (ConfigurableListableBeanFactory) method is invoked.

Now let’s look at ConfigurationClassPostProcessor source code:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor.{
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {... }}Copy the code

ConfigurationClassPostProcessor BeanDefinitionRegistryPostProcessor is achieved, which inherits the spring BeanFactoryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {... }Copy the code
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
Copy the code

Can see ConfigurationClassPostProcessor spring BeanFactoryPostProcessor and wrote postProcessBeanFactory method is realized. After the postProcessBeanFactory method is called, it processes the @Configuration class and the @bean method:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor.PriorityOrdered.ResourceLoaderAware.BeanClassLoaderAware.EnvironmentAware {...@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		int factoryId = System.identityHashCode(beanFactory);
		if (this.factoriesPostProcessed.contains(factoryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + beanFactory);
		}
		this.factoriesPostProcessed.add(factoryId);
		if (!this.registriesPostProcessed.contains(factoryId)) {
			// BeanDefinitionRegistryPostProcessor hook apparently not supported...
			// Simply call processConfigurationClasses lazily at this point then.
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}

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

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

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: "+ beanDef); }}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
				if(generator ! =null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator; }}}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);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(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);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

2) loading ImportSelector and ImportBeanDefinitionRegistrar implementation

Because ImportSelector and ImportBeanDefinitionRegistrar from Spring Frameeork3.1 began to introduce, so the 3.0 version will not appear in the implementation of the both. Because BeanDefinitionRegistryPostProcessor from Spring Framework3.0.1, begin to introduce ConfigurationClassPostprocessor implementation has been changed, Its implementing an interface from the spring BeanFactoryPostProcessor replaced with BeanDefinitionRegistryPostProcessor, And BeanDefinitionRegistryPostProcessor extension spring BeanFactoryPostProcessor interface, so ConfigurationClassPostProcessor exist two phases:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor.PriorityOrdered.ResourceLoaderAware.BeanClassLoaderAware.EnvironmentAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {... }@Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {... }}Copy the code

In addition to the two stages, ConfigurationClassPostProcessor in Spring Framework3.1, not much changes in this version of the main change or focus on the realization of ConfigurationClassParser, In its doProcessConfigurationClass (ConfigurationClass, AnnotationMetadata) method, the increased @ PropertySource and @ ComponentScan annotation processing, And update the processImport (ConfigurationClass, String [], Boolean) method implementation:

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

        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // Recursively process any member (nested) classes first
            processMemberClasses(configClass, sourceClass);
        }

        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            } else {
                logger.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();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { 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
        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
                returnsourceClass.getSuperClass(); }}// No superclass -> processing is complete
        return null; }}Copy the code
class ConfigurationClassParser {...private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        } else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine importsClass<? > candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector,this.environment, this.resourceLoader, this.registry);
                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        } else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false); }}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitionsClass<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar,this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    } else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); }}}catch (BeanDefinitionStoreException ex) {
                throw ex;
            } catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                                configClass.getMetadata().getClassName() + "]", ex);
            } finally {
                this.importStack.pop(); }}}}Copy the code

Through AssignabletypeFilter to determine whether a candidate Class yuan notes @ Import assignment ImportSelector or ImportBeanDefinitionRegistrar implementation, To decide whether to perform ImportSelector or ImportBeanDefinitionRegistrar processing, including importingClassMetadata AnnotationMetadata object is the current yuan notes @ Import.

To sum up, ConfigurationClassPostProcessor responsible for screening @ Component Class, @ the Configuration Class and @ the Bean definitions of Bean method (BeanDefinition), ConfigurationClassPrser from candidate Bean definitions of parsing, in addition to the Configuration set is then ConfigurationClassBeanDefinitionReader BeanDefinition transformation and registration.

summary

@Enable There are two implementations of module driver

  • Annotation driven
  • Interface programming

Spring Framework loaded @ the Configuration Class, ImportSelector implementation and ImportBeanDefinitionRegistrar implementation, need to introduce @ Import or @ ComponentScan.

But the Spring Framework does not have the ability to auto-assemble. We’ll talk about SpringBoot autowiring in the next section.

【 Reference to xiao Ma Ge SpringBoot programming ideas 】