SpringBoot進階之道-@Enable模塊驅動

今天來講說@Enable模塊驅動。Spring Framework是從3.1版本開始支持「@Enable模塊驅動」的。所謂「模塊」是指具有相同領域的功能組件集合,組合造成一個獨立的單元。java

圖表是Spring FrameworkSpring Bootspring Cloud的@Enable註解模塊:spring

框架實現 @Enable註解模塊 激活模塊
Spring Framework @EnableWebMvc Web Mvc模塊
@EnableTransactionManagement 事務管理模塊
@EnableCaching Caching模塊
@EnableMBeanExport JMX模塊
@EnableAsync 異步處理模塊
@EnableWebFlux Web Flux 模塊
@EnableAspectJAutoProxy AspectJ模塊
Spring Boot @EnableAutoConfiguration 自動裝配模塊
@EnableManagementContext Actuator模塊
@EnableConfigurationProperties 配置屬性綁定模塊
@EnableOAuth2Sso OAuth2 單點登錄模塊
spring Cloud @EnableEurekaServer Eureka服務模塊
@EnableConfigServer 配置服務器模塊
@EnableFeignCliens Feign客戶端模塊
@EnableZuulProxy 服務網關Zuul模塊
@EnableCircuitBreaker 服務熔斷模塊
  • 引入@Enable模塊驅動的意義在於能簡化裝配步驟,實現了「按需裝配」,同時屏蔽組件集合裝配的細節。

一、剖析@Enable 「模塊驅動」

這就要從Spring Framework3.0新引入的@Import提及。@Import用於導入一個或多個ConfigurationClass,將其註冊爲Spring Bean。這裏須要注意的是:編程

  • Spring Framework3.0中存在必定的限制,僅支持@Configuration標註的類
  • 在Spring Framework3.1中,@Import則擴大了指責範圍,還能夠用於聲明至少一個@Bean方法的類,以及ImportSelector或ImportBeanDefinitionRegistrar的實現類

估計有小夥伴已經發現了,Spring Framework提供了兩類關於@Enable模塊驅動的實現方式:服務器

  • 被@Configuration類和@Bean方法聲明的類歸類爲「註解驅動」
  • 以ImportSelector或ImportBeanDefinitionRegistrar的實現類歸類爲「接口編程」

1)首先,咱們舉個栗子,拿@EnableAsync註解來講app

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
    ...
}
複製代碼

咱們發現上面@EnableAsync的源碼標註了@Import({AsyncConfigurationSelector.class})。@Import的做用我已經說起過了,接下來點開AsyncConfigurationSelector類,看看是啥子東西:框架

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    ...
}
複製代碼

好,咱們發現AsyncConfigurationSelector繼承了AdviceModeImportSelector類,那麼咱們在點開AdviceModeImportSelector類,看看有什麼祕密:異步

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
    ...
}
複製代碼

是否是很眼熟,沒錯,AdviceModeImportSelector實現了ImportSelector類,那麼咱們是否是能夠推斷,@EnableAysnc是以「接口編程」的方式實現@Enbale模塊的。ide

2)那麼,接下來咱們再舉個栗子,看一下@EnableWebMvc註解有什麼神祕之處函數

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
複製代碼

以上源碼相信小夥伴們已經猜到了,@EnableWebMvc標註了@Import({DelegatingWebMvcConfiguration.class})。好,咱們打開DelegatingWebMvcConfiguration看看源碼是什麼:post

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    ...
}
複製代碼

有沒有看出什麼特別之處呢?DelegatingWebMvcConfiguration類是一個@Configuration的類。因此咱們同樣能夠推斷,@EnableWebMvc是以「註解驅動」實現@Enbale模塊的。

相信看到這裏,很多小夥伴已經能夠自定義@Enable模塊驅動了。

二、@Enable模塊驅動原理

前面提到過@Enable模塊驅動使用@Import實現,而且@Import的職責在於裝載導入類(Importing Class),將其定義爲Spring Bean。導入類主要爲@Configuration ClassImportSelector實現以及ImportBeanDefinitionRegistrar實現

1)裝載@Configuration Class

  • @Configuration是Spring Framework3.0開始引入的,但該版本還未引入@ComponentScan註解驅動,所以配套的導入註解是@Import。
  • 儘管Spring Framework3.0提供了註解驅動上下文實現AnnotationConfigApplicationContext,但與@Import配合比較繁瑣,僅支持Spring組件類的逐個導入,如這樣@Import({A.class,B.class,...}),所以當時仍是沒法徹底替代XML元素<context:component-scan/>
  • 即便Spring應用上下文與<context:component-scan/>結合使用,@Import的處理依舊沒法執行。所以咱們之前在開發spring項目時,同時配置XML元素<context:component-scan/><context:annotation-config/>

下面咱們分析<context:annotation-config/>所對應的BeanDefinitionParser的實現類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;
    }
}
複製代碼

咱們看源碼得知,parse(Element,ParserContext)方法調用AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry,Object)方法實現BeanDefinition(用於存儲Bean的定義信息)的實例解析。

該方法從Spring Framework3.0開始,新增了@Configuration Class的處理實現ConfigurationClassPostProcessor:

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"));
        }
        ...
        return beanDefs;
    }
}
複製代碼

從源碼我能夠看出,ConfigurationClassPostProcessor被封裝成Spring Bean定義(BeanDefinition),接着註冊爲Spring Bean,且Bean的名稱爲「org.springframework.context.annotation.internalConfigurationAnnotationProcessor」。

那麼咱們腦洞下,因此在Spring Framework中:

  • <context:annotation-config/>的底層實現類AnnotationConfigBeanDefinitionParser調用了AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)方法註冊ConfigurationClassPostProcessor Bean。
  • <context:component-scan/>的底層實現類ComponentScanBeanDefinitionParser也調用了AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)方法註冊ConfigurationClassPostProcessor Bean。

其實ConfigurationClassPostProcessor不只在XML配置驅動下才可裝配,Spring Framework3.0註解驅動上下文實現AnnotationConfigApplicationContext也能夠裝配:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    private final AnnotatedBeanDefinitionReader reader;
    ...
    public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        ...
    }
    ...
}
複製代碼

咱們繼續觀察成員變量AnnotatedBeanDefinitionReader的構造函數:

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

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        ...
        this.registry = registry;
        ...
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
}
複製代碼

咱們分析以上源碼,能夠發現AnnotationConfigApplicationContextAnnotatedBeanDefinitionReader類型成員reader在構造時,也顯式的調用了AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)方法。

在Spring應用上下文啓動中(AbstractApplicationContext#refresh()方法被調用時),Spring容器(BeanFactory)將ConfigurationClassPostProcessor初始化爲Spring Bean。它做爲BeanFactoryPostProcessor實現,隨後其postProcessBeanFactory(ConfigurableListableBeanFactory)方法被調用。

咱們先來看看ConfigurationClassPostProcessor源碼:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, ... {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        ...
    }
}
複製代碼

ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor,然後者繼承了BeanFactoryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    ...
}
複製代碼
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
複製代碼

能夠看出ConfigurationClassPostProcessor實現了BeanFactoryPostProcessor並重寫了postProcessBeanFactory方法。postProcessBeanFactory方法被調用後,隨之處理@Configuration類和@Bean方法:

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(new BeanDefinitionHolder(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(new BeanDefinitionHolder(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();
		}
	}
}
複製代碼

2)裝載ImportSelector和ImportBeanDefinitionRegistrar實現

因爲ImportSelector和ImportBeanDefinitionRegistrar從Spring Frameeork3.1纔開始引入,因此3.0版本中不會出現二者的實現。因爲BeanDefinitionRegistryPostProcessor從Spring Framework3.0.1開始引入,ConfigurationClassPostprocessor的實現也隨之發生變化,其實現接口從BeanFactoryPostProcessor替換爲BeanDefinitionRegistryPostProcessor,且BeanDefinitionRegistryPostProcessor擴展了BeanFactoryPostProcessor接口,因此ConfigurationClassPostProcessor存在兩個階段:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        ...
    }
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    	...
    }
}
複製代碼

除兩階段實現外,ConfigurationClassPostProcessor在Spring Framework3.1中並無太多變化,該版本的主要變化仍是集中在ConfigurationClassParser的實現上,在其doProcessConfigurationClass(ConfigurationClass,AnnotationMetadata)方法中,增長了@PropertySource和@ComponentScan註解處理,而且更新了processImport(ConfigurationClass,String[],boolean)方法的實現:

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

        // No superclass -> processing is complete
        return null;
    }
}
複製代碼
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 imports
                        Class<?> 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 definitions
                        Class<?> 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();
            }
        }
    }
}	
複製代碼

經過AssignabletypeFilter判斷候選Class元註解@Import是否賦值ImportSelector或ImportBeanDefinitionRegistrar實現,從而決定是否執行ImportSelector或ImportBeanDefinitionRegistrar處理,其中importingClassMetadata就是當前元註解@Import的AnnotationMetadata對象。

綜上所述,ConfigurationClassPostProcessor負責篩選@Component Class、@Configuration Class及@Bean方法的Bean定義(BeanDefinition),ConfigurationClassPrser則從候選的Bean定義中解析除Configuration集合,隨後被ConfigurationClassBeanDefinitionReader轉化並註冊BeanDefinition。

小結

@Enable 「模塊驅動」有兩種實現方式

  • 註解驅動
  • 接口編程

Spring Framework裝載@Configuration ClassImportSelector實現以及ImportBeanDefinitionRegistrar實現時,需引入@Import或@ComponentScan。

但Spring Framework並不具有自動裝配的能力。下節將說說SpringBoot自動裝配。

【參考小馬哥SpringBoot編程思想】

相關文章
相關標籤/搜索