Understand the @enable module driver

Customize the @enable module driver

@EnableWebMvcThe basic thinking about

We all know the @enableWebMVC annotation

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

The comments we concern and @ Import yuan mark content, it will be registered DelegatingWebMvcConfiguration called Bean in the Spring. (DelegatingWebMvcConfiguration must add @ the Configuration notes)

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if(! CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers); }}Copy the code

Implement @enable module based on “annotation driven”

  1. Start by defining a class annotated by @Configuration

    @Configuration
    public class HelloWorldConfiguration {
        @Bean
        public String helloWorld(a){ // Create a Bean named "helloWorld" String
            return "Hello,World"; }}Copy the code
  2. Define the @enableHelloWorld annotation

    /** * echo {@linkEnableWebMvc} programming mode written by Enable*** annotations *@author ajin
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(HelloWorldConfiguration.class)
    public @interface EnableHelloWorld {
    
    }
    Copy the code
  3. Add the @enableHelloWorld annotation to the Spring Boot Boot class

    @EnableHelloWorld
    @Configuration
    public class AnnotationdesignApplication {
    
        public static void main(String[] args) {
            // Build the Annotation configuration driving the Spring context
            AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
            // Register the current boot class (annotated by @Configuration) into the Spring context
            context.register(AnnotationdesignApplication.class);
            // Start the context
            context.refresh();
            // Get the Bean object named "helloWorld"
            String helloWorld = context.getBean("helloWorld",String.class);
            // Output user name "HelloWorld"
            System.out.printf("helloWorld = %s \n" ,helloWorld);
            // Close the contextcontext.close(); }}Copy the code
  4. Bootstrap class

    The console output is as follows:

    helloWorld = Hello,World

Based on “interface programming” implementation@EnableThe module

Based on the interface programming, need to implement ImportSelector or ImportBeanDefinitionRegistrar interface

ImportSelectorProgramming model

@EnableCachingunderstand

First let’s look at the implementation of @enablecaching.

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

CachingConfigurationSelector

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {...@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null; }}}Copy the code

The annotation programming mode for @enablecaching is to implement the ImportSelector interface and override its selectImports(AnnotationMetadata…). Have no direct implementation method, and CachingConfigurationSelector ImportSelector interface, but through AdviceModeImportSelector implementation.

The A generic represents A Java Annotation, because any Java Annotation inherits the Annotation interface

public abstract class AdviceModeImportSelector<A extends Annotation> implements  ImportSelector{...@Override
	public finalString[] selectImports(AnnotationMetadata importingClassMetadata) { Class<? > annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class); Assert.state(annType ! =null."Unresolvable type argument for AdviceModeImportSelector");

		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
		if (attributes == null) {
			throw new IllegalArgumentException(String.format(
					"@%s is not present on importing class '%s' as expected",
					annType.getSimpleName(), importingClassMetadata.getClassName()));
		}

		AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
		String[] imports = selectImports(adviceMode);
		if (imports == null) {
			throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
		}
		return imports;
	}
    
    @Nullable
	protected abstract String[] selectImports(AdviceMode adviceMode);

}
Copy the code
Self actualization
  1. Create a Server interface Server

    /** * Server interface *@author ajin
     */
    public interface Server {
        /** * start the server ** /
        void start(a);
        /** * Shut down the server ** /
        void stop(a);
    
        /** * Server type ** /
        enum  Type{
            HTTP, // HTTP server
            FTP  // FTP server}}Copy the code
  2. Implementing the Server Interface

    / * * *@author ajin
     */
    @Component // Ensure implementation as a Spring Bean according to the ImportSelector contract
    public class HttpServer implements Server {
    
    
        @Override
        public void start(a) {
            System.out.println("HTTP server starting...");
        }
    
        @Override
        public void stop(a) {
            System.out.println("HTTP server down..."); }}Copy the code
    * * *@author ajin
     */
    @Component
    public class FtpServer implements Server {
    
        @Override
        public void start(a) {
            System.out.println("FTP server starting...");
        }
    
        @Override
        public void stop(a) {
            System.out.println("FTP server down..."); }}Copy the code
  3. Define @ EnableServer

    / * * *@author ajin
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(ServerImportSelector.class) / / import ServerImportSelector
    public @interface EnableServer {
    
        /** * Sets the server type ** /
        Server.Type type(a);
    }
    
    Copy the code
  4. Implement ServerImportSelector class

    / * * *@author ajin
     * @see EnableServer
     */
    public class ServerImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // Read all the attribute methods in EnableServer, only the type() attribute method in this example
            // key: the name of the attribute method value: the object returned by the attribute method
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableServer.class.getName());
            // Get a property method named type and force it to type server.type
            Server.Type type = (Server.Type) annotationAttributes.get("type");
            // An array of imported class names
            String[] importClassNames = new String[0];
            switch (type) {
                case FTP:
                    importClassNames = new String[]{FtpServer.class.getName()};
                    break;
                case HTTP:
                    importClassNames = new String[]{HttpServer.class.getName()};
                    break;
            }
            returnimportClassNames; }}Copy the code
  5. Define the bootstrap class and execute the main method

    @Configuration
    @EnableServer(type = Server.Type.HTTP) // Set up the Http server
    public class EnableServerBootstrap {
    
        public static void main(String[] args) {
            // Build Annotation configuration-driven Spring context
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
            // Register the current boot class
            context.register(EnableServerBootstrap.class);
            // Start the context
            context.refresh();
    
            // Get the Server Bean object, in this case HttpServer
            Server server= context.getBean(Server.class);
            // Start the server
            server.start();
            // Stop the serverserver.stop(); context.close(); }}Copy the code

The console output is:

The HTTP server is starting… The HTTP server is down…

We essentially add the Type attribute to the @Enableserver annotation, and the ServerImportSelector class gets the value of our Type attribute and selects which Bean(HttpServer/FtpServer) to register into Spring based on that value.

ImportBeanDefinitionRegistrarProgramming model

Self actualization

We made a few changes based on the ImportSelector self-implementation part of the code.

  1. Modify the EnableServer comment

    / * * *@author ajin
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    / / @ Import (ServerImportSelector. Class) / / Import ServerImportSelector
    @Import(ServerImportBeanDefinitionRegistrar.class)
    public @interface EnableServer {
    
        /** * Sets the server type ** /
        Server.Type type(a);
    }
    Copy the code
  2. New ServerImportBeanDefinitionRegistrar class

    public class ServerImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // reuse {@link ServerImportSelector} implementation, reduce repeated encoding
            ImportSelector importSelector = new ServerImportSelector();
            // Filter the collection of Class names
            String[] selectClassNames = importSelector.selectImports(importingClassMetadata);
            // Create the Bean definition
            Stream.of(selectClassNames)
                    // Convert to BeanDefinitionBuilder object
                    .map(BeanDefinitionBuilder::genericBeanDefinition)
                    // Convert to BeanDefinition
                    .map(BeanDefinitionBuilder::getBeanDefinition)
                    .forEach(beanDefinition ->
                            // Register BeanDefinitionRegistry with BeanDefinitionRegistryBeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition,registry) ); }}Copy the code
  3. Start the bootstrap class EnableServerBootstrap

The console output is as follows:

The HTTP server is starting… The HTTP server is down…

ImportBeanDefinitionRegistrar needs not only developers choose to register what Bean, also will Bean manually register to the Spring context.

@EnableModule driving principle

understand@ConfigurationClass loading

We need to take a closer look at how the @Configuration annotated Java class is registered as a Bean in the Spring context.

ComponentScanBeanDefinitionParser

@Configuration has an @Component meta annotation, The @Configuration class can be registered with Spring as a Bean either by @ComponetScan or by configuring < Context: Component-scan > in an XML file.

We first understand < context: component – scan > ComponentScanBeanDefinitionParser parsing. Before you understand, don’t you ask yourself why you’re dealing with this class?

We open the IDEA, looking for me this Spring Boot even Spring applications must rely on the jar package – > Spring – the context – XXX. Jar, find the meta-inf \ Spring handles file, as shown below:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
Copy the code

The tag handler class, ContextNamespaceHandler, is defined

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init(a) {
		registerBeanDefinitionParser("property-placeholder".new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override".new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config".new AnnotationConfigBeanDefinitionParser());
        / / see ComponentScanBeanDefinitionParser here
		registerBeanDefinitionParser("component-scan".new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver".new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured".new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export".new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server".newMBeanServerBeanDefinitionParser()); }}Copy the code
ComponentScanBeanDefinitionParser#parse()

This method is the parsing of the < Context: Component-scan > tag

If I were a designer of the Spring framework (and OF course I am a chicken), I would first have to get the information in the tag and then do something based on that information.

This tag declares packages that tell the Spring framework to scan them and register the beans of the Component class in the Spring context.

Let’s verify our poor analysis haha

@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        // Get the packet to scan
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        // Get the 'Component' classes in these packages and generate BeanDefinitionHolder objects
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        // Register the Bean into the Spring context
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}
Copy the code

RegisterComponents focus

protected void registerComponents( XmlReaderContext readerContext, Set
       
         beanDefinitions, Element element)
        {

		Object source = readerContext.extractSource(element);
    	/ / CompositeComponentDefinition encapsulates many ComponentDefinition,
    	// ComponentDefinition is defined by BeanDefinitionHolder
		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
		// Here is our ComponentScan associated Bean
		for(BeanDefinitionHolder encapsulation and beanDefHolder: beanDefinitions) {compositeDef. AddNestedComponent (new BeanComponentDefinition(beanDefHolder));
		}

		// Register annotation config processors, if necessary.
		boolean annotationConfig = true;
		if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
			annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
		}
		if (annotationConfig) {
            // Pay special attention
			Set<BeanDefinitionHolder> processorDefinitions =    AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
			for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
				compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
			}
		}
		
		readerContext.fireComponentRegistered(compositeDef);
	}
Copy the code
AnnotationConfigUtils#registerAnnotationConfigProcessors

This is an important method that generates a series of BeandefinitionHolders

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

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

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

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

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

		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

AnnotationConfigBeanDefinitionParser

This class is used to parse the < Context :annotation-config/> tag, which is used to register some BeanPostProcessors for our annotation-based programming.

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor

The parse () method of the core or AnnotationConfigUtils. RegisterAnnotationConfigProcessors

public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		Object source = parserContext.extractSource(element);

		// Obtain bean definitions for all relevant BeanPostProcessors.
		Set<BeanDefinitionHolder> processorDefinitions =
				AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

		// Register component for the surrounding <context:annotation-config> element.
		CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
		parserContext.pushContainingComponent(compDefinition);

		// Nest the concrete beans in the surrounding component.
		for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
			parserContext.registerComponent(new BeanComponentDefinition(processorDefinition));
		}

		// Finally register the composite component.
		parserContext.popAndRegisterContainingComponent();

		return null; }}Copy the code

We see AnnotationConfigUtils registerAnnotationConfigProcessors core part of its source

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(... BeanDefinitionRegistry registry,@Nullable Object source){
    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		if(! registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =newRootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); }... }Copy the code

Here to generate a type of ConfigurationClassPostProcessor RootBeanDefinition, behind will be registered as Bean in the Spring.

And just

is also registered ConfigurationClassPostProcessor Bean, Because when its resolution is also called AnnotationConfigUtils# registerAnnotationConfigProcessors () method.

Actually talking about now is the XML configuration context scenarios (ClassPathXmlApplicationContext), so for annotation driven context?

ConfigurationClassPostProcessor AnnotationConfigApplicationContext is how to register

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

	private finalAnnotatedBeanDefinitionReader reader; . }Copy the code

AnnotatedBeanDefinitionReader constructor is as follows:

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);
        / / register the ConfigurationClassPostProcessor here
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

Copy the code

Simple summary

Whether it’s an XML configuration-driven context or an annotation-driven context, By AnnotationConfigUtils. RegisterAnnotationConfigProcessors ConfigurationClassPostProcessor is registered as a Spring Bean.

ConfigurationClassPostProcessor

Let’s understand when this Bean is called back as a BeanFactoryPostProcessor and what it does when called back.

The callback time

AbtractApplicationContext# refresh () method calls the invokeBeanFactoryPostProcessors method, This method calls back to the BeanFactoryPostProcessor#postProcessBeanFactory method as follows:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    // Here is the core method
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

		// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
		// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
		if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(newContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }}Copy the code
  • PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    • PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors

      private static void invokeBeanFactoryPostProcessors( Collection
                  postProcessors, ConfigurableListableBeanFactory beanFactory) {
      
      		for(BeanFactoryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanFactory(beanFactory); }}Copy the code
The callback logic

The above analysis, we have a general idea of the timing of the callback, now let’s look at the logic of the callback method

   // ConfigurationClassPostProcessor
   // Prepare the Configuration class to service Bean requests at run time by replacing the Configuration class with a CGLIB enhanced subclass.
    @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.
            // Core method
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}
		// Enhanced functionality
		enhanceConfigurationClasses(beanFactory);
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}
Copy the code
ConfigurationClassPostProcessor#processConfigBeanDefinitions

The main logic of this method is to parse the @bean in the @Configuration class and save a Map with both key values as ConfigurationClass

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! =null) {
				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());
			returnInteger.compare(i1, i2); }); (Bean)// 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(
						AnnotationConfigUtils.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 {
            / / parsing BeanDefinitionHolder (beans)
			parser.parse(candidates);
			parser.validate();
			// Get the ConfigurationClass generated by parsing the @Configuration class
			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());
			}
            // Register the Bean into the Spring context according to ConfigurationClass
			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
  1. ConfigurationClassParser#parse()Methods.
public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else{ parse(bd.getBeanClassName(), holder.getBeanName()); }}catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); }}this.deferredImportSelectorHandler.process();
	}
Copy the code

The parse () will call according to different situations two overloaded parse method, one is based on additional implement StandardAnnotationMetadata AnnotationMetadataReadingVisitor and Java reflection

  • parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());

    • processConfigurationClass

      protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
      		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      			return;
      		}
      
      		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 {
      			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
      		}
      		while(sourceClass ! =null);
      
      		this.configurationClasses.put(configClass, configClass);
      	}
      Copy the code
      • doProcessConfigurationClass

        @Nullable
        	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()); }}}}// 1. Process any @Import annotations
        		processImports(configClass, sourceClass, getImports(sourceClass), true);
        
        		// 2. 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); }}// 3. 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
  1. ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
/**
	 * Read a particular {@link ConfigurationClass}, registering bean definitions
	 * for the class itself and all of its {@link Bean} methods.
	 */
	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 (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}
Copy the code

aboutConfigurationClassPostProcessorSee Chapter 8, Spring Annotation-driven Design Patterns, in Spring Boot Programming Ideas for more details

conclusion

@ the Enable module driven like a switch to the Spring framework, we know the first ConfigurationClassPostProcessor assembly, Namely AnnotationConfigUtils# registerAnnotationConfigProcessors work.

In our @enable mode, we generally create an @enable annotation, and then hand the class to Spring to determine which Bean to selectively load through the @import meta annotation, which is itself a special case of conditional assembly. The @enableWebMVC exception is to load the Bean into the Spring container.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
Copy the code
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if(! CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers); }}... }Copy the code

Our class is usually be @ the Configuration and the label @ Enable at the same time, we want to make the class in the @ Import is processed by a Spring, you need to use ConfigurationClassPostProcessor to deal with, it is mainly to do the following a few things

  • Parse BeanDefinitionHolder(beans saved in the Spring context) to generate ConfigurationClass

    • Process any @Import annotations

    • Process individual @Bean methods

      This is the normal @Configuration&& @bean model

  • Gets the ConfigurationClass generated by parsing the @Configuration class

  • The Bean is registered with the information provided by the ConfigurationClass