SpringBoot 自动配置原理源码分析

1. 自动配置的实现

SpringBoot 的自动配置机制为快速开发提供了极大的帮助,众所周知,其采用的是约定优于配置的理念。简单说就是把常用的三方工具预先配置好,这样开发人员只要处理好 jar 包依赖,再少量配置核心参数就可以使用三方工具了。事实上,SpringBoot 实现所谓的约定主要依赖以下两个关键

  1. 开启自动配置的注解 @EnableAutoConfiguration
  2. META-INF/spring.factoriesMETA-INF/spring-autoconfigure-metadata.properties文件。这两个文件主要用于配置接口的元数据及其实现类名称,是一种 SPI 机制,在程序中读取这些配置之后就可以确定接口的实现类

以下为 SpringBoot 实现自动配置的源码流程图,其中关于 ConfigurationClassParser 解析配置类的触发路径读者可阅读前置博客 SpringBoot 注解 @Import 的原理-ConfigurationClassPostProcessor 源码解析。其实本文可以看作是对这篇博客的补充,因为本文所涉及的流程已经包含在这篇博客中,只不过没有具体分析而已

读者需注意,在使用 SpringBoot 框架时必须要有一个观念,那就是框架中的大部分注解其实都对应了一个 BeanPostProcessor 接口的实现,这个接口是 Spring 框架实现 AOP 最重要的手段

在这里插入图片描述

2. 源码分析

2.1 @EnableAutoConfiguration 引入的 AutoConfigurationImportSelector 选择器类的解析

  1. SpringBoot 框架中,每个程序都有一个启动类,启动类由 @SpringBootApplication 注解标注出来。我们都知道这个注解其实是由 3 个注解组成,其中一个就是@EnableAutoConfiguration,以下即为其定义。这个注解其实没有太多内容,在本文中需要关注的就是其通过 @Import注解引入了配置选择器类 AutoConfigurationImportSelector

    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
     String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
     /**
      * Exclude specific auto-configuration classes such that they will never be applied.
      * @return the classes to exclude
      */
     Class<?>[] exclude() default {};
    
     /**
      * Exclude specific auto-configuration class names such that they will never be
      * applied.
      * @return the class names to exclude
      * @since 1.3.0
      */
     String[] excludeName() default {};
    
    }
    
  2. @Import注解背后的实现机制是完成自动配置的关键,读者可先阅读SpringBoot 注解 @Import 的原理-ConfigurationClassPostProcessor 源码解析 一文,本文直接从 ConfigurationClassParser#parse() 方法解析配置类的触发点开始分析。可以看到,该方法其实主要调用了ConfigurationClassParser内部的其他重载方法

    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();
     }
    
    protected final void parse(@Nullable String className, String beanName) throws IOException {
     	Assert.notNull(className, "No bean class name for configuration class bean definition");
     	MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
     	processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
     }
    
     protected final void parse(Class<?> clazz, String beanName) throws IOException {
     	processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
     }
    
     protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
     	processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
     }
    
  3. 以上不管哪个 ConfigurationClassParser#parse() 方法被调用,最后都会调用到 ConfigurationClassParser#processConfigurationClass() 方法。这个方法内部会在 while 循环中不断调用 ConfigurationClassParser#doProcessConfigurationClass() 方法进行解析配置类的操作

    protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) 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, filter);
     	do {
     		sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
     	}
     	while (sourceClass != null);
    
     	this.configurationClasses.put(configClass, configClass);
     }
    
  4. ConfigurationClassParser#doProcessConfigurationClass() 方法的实现流程比较清晰,主要是解析各个注解引入的配置类。本文主要关注解析 @Import 注解的操作,其处理主要分为了两个步骤:

    1. ConfigurationClassParser#getImports() 方法去获取当前处理的类上的 @Import 引入的类
    2. ConfigurationClassParser#processImports() 方法处理 @Import 引入的类
    @Nullable
     protected final SourceClass doProcessConfigurationClass(
     		ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
     		throws IOException {
    
     	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
     		// Recursively process any member (nested) classes first
     		processMemberClasses(configClass, sourceClass, filter);
     	}
    
     	// 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), filter, 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;
     }
    
  5. ConfigurationClassParser#getImports() 方法的逻辑很轻量,就是递归获取目标类上所有 @Import 注解,并将该注解引入的类收集起来,本节步骤1 引入的 AutoConfigurationImportSelector 在这个步骤就会被检测出来

    private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
     	Set<SourceClass> imports = new LinkedHashSet<>();
     	Set<SourceClass> visited = new LinkedHashSet<>();
     	collectImports(sourceClass, imports, visited);
     	return imports;
     }
     
    private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
     		throws IOException {
    
     	if (visited.add(sourceClass)) {
     		for (SourceClass annotation : sourceClass.getAnnotations()) {
     			String annName = annotation.getMetadata().getClassName();
     			if (!annName.equals(Import.class.getName())) {
     				collectImports(annotation, imports, visited);
     			}
     		}
     		imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
     	}
     }
    
  6. ConfigurationClassParser#processImports() 方法的处理此处不做详细分析,简单来说这个步骤会分别解析 @Import 注解引入的 3 种不同的类

    由于AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口需要延后处理,则在此处其实不会直接回调其 AutoConfigurationImportSelector#selectImports() 方法,而是调用 this.deferredImportSelectorHandler.handle()将其缓存起来

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
     		Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
     		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 = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
     							this.environment, this.resourceLoader, this.registry);
     					Predicate<String> selectorFilter = selector.getExclusionFilter();
     					if (selectorFilter != null) {
     						exclusionFilter = exclusionFilter.or(selectorFilter);
     					}
     					if (selector instanceof DeferredImportSelector) {
     						this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
     					}
     					else {
     						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
     						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
     						processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, 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 =
     							ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
     									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), exclusionFilter);
     				}
     			}
     		}
     		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();
     		}
     	}
     }
    
  7. DeferredImportSelectorHandler#handle() 的源码如下,主要操作的流程如下:

    1. 假如其内部 this.deferredImportSelectors属性为 null,则直接将 DeferredImportSelector 实例注册进 DeferredImportSelectorGroupingHandler 实例,处理导入的类
    2. 如果内部 this.deferredImportSelectors属性不为 null,则将其添加进缓存列表即可,通常是进入该分支处理
    public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
     		DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
     		if (this.deferredImportSelectors == null) {
     			DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
     			handler.register(holder);
     			handler.processGroupImports();
     		}
     		else {
     			this.deferredImportSelectors.add(holder);
     		}
     	}
    
  8. 对于实现了 DeferredImportSelector 接口的选择器类,最终触发其相关方法需要回到本节步骤2 ConfigurationClassParser#parse() 方法最后一行,即 this.deferredImportSelectorHandler.process() 调用 DeferredImportSelectorHandler#process() 方法。这个方法的处理步骤如下:

    1. 首先新建 DeferredImportSelectorGroupingHandler 对象作为处理器
    2. 调用DeferredImportSelectorGroupingHandler#register()将缓存的 DeferredImportSelector 选择器类依次注册到处理器中
    3. 调用处理器方法 DeferredImportSelectorGroupingHandler#processGroupImports()完成选择器类中配置的类的解析
    public void process() {
     		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
     		this.deferredImportSelectors = null;
     		try {
     			if (deferredImports != null) {
     				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
     				deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
     				deferredImports.forEach(handler::register);
     				handler.processGroupImports();
     			}
     		}
     		finally {
     			this.deferredImportSelectors = new ArrayList<>();
     		}
     	}
    
  9. DeferredImportSelectorGroupingHandler#register() 方法的处理如下所示

    1. 首先 deferredImport.getImportSelector().getImportGroup() 将调用DeferredImportSelector#getImportGroup() 方法获取导入组,对 AutoConfigurationImportSelector 来说此方法返回一个内部类 AutoConfigurationGroup
    2. DeferredImportSelectorGroupingHandler#createGroup() 方法创建一个 AutoConfigurationGroup对象,并将其包装为 DeferredImportSelectorGrouping
    public void register(DeferredImportSelectorHolder deferredImport) {
     		Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
     		DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
     				(group != null ? group : deferredImport),
     				key -> new DeferredImportSelectorGrouping(createGroup(group)));
     		grouping.add(deferredImport);
     		this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
     				deferredImport.getConfigurationClass());
     	}
    
  10. DeferredImportSelectorGroupingHandler#processGroupImports() 方法的逻辑清晰易懂

    1. 通过上一步注册的包装类 DeferredImportSelectorGrouping对象调用 DeferredImportSelectorGrouping#getImports() 方法去获取选择器配置的类。对于自动配置而言,这一步其实最后就是调用到 AutoConfigurationGroup#process() 方法解析自动配置文件,之后调用AutoConfigurationGroup#selectImports() 返回解析结果, 下节将详细分析此关键步骤
    2. 遍历上一步解析获得的配置类集合,继续调用ConfigurationClassParser#processImports() 方法,将解析获取的导入的类名entry.getImportClassName() 包装为候选类入参,完成导入的配置类的解析
    public void processGroupImports() {
    		for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    			Predicate<String> exclusionFilter = grouping.getCandidateFilter();
    			grouping.getImports().forEach(entry -> {
    				ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
    				try {
    					processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
    							Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
    							exclusionFilter, false);
    				}
    				catch (BeanDefinitionStoreException ex) {
    					throw ex;
    				}
    				catch (Throwable ex) {
    					throw new BeanDefinitionStoreException(
    							"Failed to process import candidates for configuration class [" +
    									configurationClass.getMetadata().getClassName() + "]", ex);
    				}
    			});
    		}
    	}
    
    public Iterable<Group.Entry> getImports() {
    		for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    			this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    					deferredImport.getImportSelector());
    		}
    		return this.group.selectImports();
    	}
    

2.2 spring.factories 自动配置文件的解析处理

  1. AutoConfigurationGroup#process() 方法是将自动配置文件中的配置类加载进 Spring 容器的关键,其处理主要分为以下几步:

    1. AutoConfigurationGroup#getAutoConfigurationMetadata() 方法加载自动配置类的元数据文件META-INF/spring-autoconfigure-metadata.properties
    2. AutoConfigurationImportSelector#getAutoConfigurationEntry()根据元数据配置去加载过滤META-INF/spring.factories 文件中的自动配置类
    3. 将解析到的自动配置类信息添加到缓存,当AutoConfigurationGroup#selectImports()方法被调用时返回
     public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
     		Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
     				() -> String.format("Only %s implementations are supported, got %s",
     						AutoConfigurationImportSelector.class.getSimpleName(),
     						deferredImportSelector.getClass().getName()));
     		AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
     				.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
     		this.autoConfigurationEntries.add(autoConfigurationEntry);
     		for (String importClassName : autoConfigurationEntry.getConfigurations()) {
     			this.entries.putIfAbsent(importClassName, annotationMetadata);
     		}
     	}
    
  2. AutoConfigurationGroup#getAutoConfigurationMetadata() 方法其实就是个包装,最后会调用到 AutoConfigurationMetadataLoader.loadMetadata() 方法加载自动配置类元数据

    private AutoConfigurationMetadata getAutoConfigurationMetadata() {
     		if (this.autoConfigurationMetadata == null) {
     			this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
     		}
     		return this.autoConfigurationMetadata;
     	}
    
  3. AutoConfigurationMetadataLoader.loadMetadata() 方法的实现如下,可以看到其逻辑主要是从类加载器根目录下加载所有的 META-INF/spring-autoconfigure-metadata.properties 文件,并将其中配置的属性包装为自动配置类元数据对象

    protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
    
    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
     	return loadMetadata(classLoader, PATH);
     }
    
     static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
     	try {
     		Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
     				: ClassLoader.getSystemResources(path);
     		Properties properties = new Properties();
     		while (urls.hasMoreElements()) {
     			properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
     		}
     		return loadMetadata(properties);
     	}
     	catch (IOException ex) {
     		throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
     	}
     }
     static AutoConfigurationMetadata loadMetadata(Properties properties) {
     	return new PropertiesAutoConfigurationMetadata(properties);
     }
    
  4. 以下节选 SpringBoot 自动配置包 META-INF/spring-autoconfigure-metadata.properties 文件中响应式服务器工厂自动配置类配置 ReactiveWebServerFactoryAutoConfiguration 来解释其含义

    // 该配置等于号后面为空,表示没有为这个键配置对应的值
    org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration=
    // 该配置表示这个 ReactiveWebServerFactoryAutoConfiguration 配置类可应用的前提是 web 应用的类型为 REACTIVE
    org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration.ConditionalOnWebApplication=REACTIVE
    // 该配置表示 ReactiveWebServerFactoryAutoConfiguration 配置类可应用的前提是必须存在类 ReactiveHttpInputMessage
    org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration.ConditionalOnClass=org.springframework.http.ReactiveHttpInputMessage
    
  5. AutoConfigurationImportSelector#getAutoConfigurationEntry()的处理步骤如下:

    1. AutoConfigurationImportSelector#getCandidateConfigurations() 方法去加载自动配置文件中配置的类名
    2. AutoConfigurationImportSelector#getExclusions() 获取代码中 exclude 排除掉的类名,将其从缓存集合中去掉
    3. AutoConfigurationImportSelector#filter() 根据上一步获取的配置类元数据检查配置类的前置条件(例如ConditionalOnClass)是否满足,不满足的也去除掉
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
     		AnnotationMetadata annotationMetadata) {
     	if (!isEnabled(annotationMetadata)) {
     		return EMPTY_ENTRY;
     	}
     	AnnotationAttributes attributes = getAttributes(annotationMetadata);
     	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
     	configurations = removeDuplicates(configurations);
     	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
     	checkExcludedClasses(configurations, exclusions);
     	configurations.removeAll(exclusions);
     	configurations = filter(configurations, autoConfigurationMetadata);
     	fireAutoConfigurationImportEvents(configurations, exclusions);
     	return new AutoConfigurationEntry(configurations, exclusions);
     }
    
  6. AutoConfigurationImportSelector#getCandidateConfigurations() 方法其实也是个包装方法,最后会调用到 SpringFactoriesLoader.loadFactoryNames() 方法

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
     	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
     			getBeanClassLoader());
     	Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
     			+ "are using a custom packaging, make sure that file is correct.");
     	return configurations;
     }
    
  7. SpringFactoriesLoader.loadFactoryNames() 方法实现如下,其逻辑主要是从类加载器根目录下加载所有的 META-INF/spring.factories 文件,并将其中配置的属性解析为接口名及其实现类名,以键值对形式存储

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
     	String factoryTypeName = factoryType.getName();
     	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
     }
     
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
     	MultiValueMap<String, String> result = cache.get(classLoader);
     	if (result != null) {
     		return result;
     	}
    
     	try {
     		Enumeration<URL> urls = (classLoader != null ?
     				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
     				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
     		result = new LinkedMultiValueMap<>();
     		while (urls.hasMoreElements()) {
     			URL url = urls.nextElement();
     			UrlResource resource = new UrlResource(url);
     			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
     			for (Map.Entry<?, ?> entry : properties.entrySet()) {
     				String factoryTypeName = ((String) entry.getKey()).trim();
     				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
     					result.add(factoryTypeName, factoryImplementationName.trim());
     				}
     			}
     		}
     		cache.put(classLoader, result);
     		return result;
     	}
     	catch (IOException ex) {
     		throw new IllegalArgumentException("Unable to load factories from location [" +
     				FACTORIES_RESOURCE_LOCATION + "]", ex);
     	}
     }
    
  8. 以下为节选 SpringBoot 自动配置包 META-INF/spring.factories 文件部分内容,可以看到以 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为键的属性配置了很多实现类,这些实现类以逗号隔开,解析完成会进行相关条件的过滤筛选。经过本节所分析的处理,这些自动配置类的类名会暴露给 Spring 容器,后续的处理过程回到上一节步骤10完成配置类的解析

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    ......
    org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
    ......
    

2.3 自动配置原理的应用

经过以上分析,相信读者对 SpringBoot 的自动配置原理已经有了一定的了解,那么应该会比较容易想到第三方工具集成到 SpringBoot 的实现方式:

  1. 基于 @Import 注解
    第三方工具包中提供一个注解引入其内部的配置类,例如 apollo 框架的 @EnableApolloConfig,在该注解上使用了 @Import 引入自动配置类
  2. 基于 @EnableAutoConfiguration 注解
    第三方工具包在根目录下添加一个 META-INF/spring.factories 文件,仿照 SpringBoot 的spring.factories 文件进行相关配置即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值