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 Enable
Simplify assembly steps
To 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 as
annotation-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 】