联系博主


你的名字:
Email:
建议:

SpringBoot自动配置原理

编辑时间:2019-07-30      赞:0       踩:0

导言:

    本文主要简述SpringBoot自动装配的原理。网上虽然有很多讲述SpringBoot自动装配原理,我查看了很多相关的资料,发现很多文章没有把SpringBoot的来龙去脉讲清楚。因此,我觉得还是有必要简述一下SpringBoot自动装配的原理,顺便整理一下我所查阅的相关资料。

正文:

    在使用SpringBoot的JavaWeb工程中,我们常常会在main方法所在的主配置类加上@SpringBootApplication的注解。@SpringBootApplication除了标注当前工程为SpringBoot应用,还实现了某些SpringBoot的功能,其中就包含了开启SpringBoot自动配置的功能。以下为@SpringBootApplication注解的定义:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
//开启自动配置
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//略
}

    由于本文是讲述自动配置相关的内容,我们把目光放在开启SpringBoot自动装配的注解@EnableAutoConfiguration上。@EnableAutoConfiguration的定义如下:

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//扫描主配置类(持有main方法的类)同级目录以及子目录下的包
@AutoConfigurationPackage
//把EnableAutoConfigurationImportSelector加载进Spring容器
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//略
}
    @EnableAutoConfiguration注解中起作用的主要是@AutoConfigurationPackage和@Import(EnableAutoConfigurationImportSelector.class)这两个注解,@AutoConfigurationPackage用于将spring boot主配置类所在的包及其子包下的所有的组件扫描到spring容器中去(注:这也是在默认情况下,Controller和Service等组件需要放在主配置类的同级或子目录下的原因),而@Import(EnableAutoConfigurationImportSelector.class)则是将EnableAutoConfigurationImportSelector这个类加载进Spring容器。
    本文基于 springboot autoconfigure 1.5.x ,在最新的 springboot autoconfigure 2.1.x 中EnableAutoConfigurationImportSelector已经被移除,转向直接使用AutoConfigurationImportSelector,而且AutoConfigurationImportSelector的代码跟本文中提到的实现也已经不同。但设计目的和主流程不变。

    以下是EnableAutoConfigurationImportSelector的实现:

@Deprecated
public class EnableAutoConfigurationImportSelector
		extends AutoConfigurationImportSelector {
	//略
}

    EnableAutoConfigurationImportSelector只是简单地重载了isEnable函数,并没有触碰到自动配置的核心,而EnableAutoConfigurationImportSelector是继承自AutoConfigurationImportSelector的。如果看过其它关于自动配置原理的文章,我相信大家都知道AutoConfigurationImportSelector便是自动配置的核心实现类。以下为AutoConfigurationImportSelector的部分比较重要的代码:

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
//略
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
            //从配置文件中加载 AutoConfigurationMetadata
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
            //从配置文件中加载 AutoConfigurationMetadata,重点
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
            // 应用过滤器AutoConfigurationImportFilter,
			// 对于 spring boot autoconfigure,定义了一个需要被应用的过滤器 :
			// org.springframework.boot.autoconfigure.condition.OnClassCondition,
			// 此过滤器检查候选配置类上的注解@ConditionalOnClass,如果要求的类在classpath
			// 中不存在,则这个候选配置类会被排除掉
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}
     
    /**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
        // 使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的
			// META-INF\spring.factories,找出其中key为
			// org.springframework.boot.autoconfigure.EnableAutoConfiguration 
			// 的属性定义的工厂类名称。

		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;
	}
//略
}

public abstract class SpringFactoriesLoader {
//略
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	/**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * @param factoryClass the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @see #loadFactories
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 */
    /*
    该方法的主要作用为查找classpath上所有jar包中的META-INF\spring.factories,找出其中key为
org.springframework.boot.autoconfigure.EnableAutoConfiguration 的属性定义的工厂类名称。
    */
	public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		try {
            //FACTORIES_RESOURCE_LOCATION 为"META-INF/spring.factories"
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			List<String> result = new ArrayList<String>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
				String factoryClassNames = properties.getProperty(factoryClassName);
				result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
			}
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
					"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
//略
}

    由于AutoConfigurationImportSelector和SpringFactoriesLoader的代码联系比较紧密,我就把这两个类的关键代码放在一起了。通过以上代码,我们不难发现,AutoConfigurationImportSelector在selectImports()方法中使用了内部工具SpringFactoriesLoader,查找classpath上所有jar包中的 META-INF\spring.factories,找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration 的属性定义的自动配置类名称。以下是spring.factories的一个自动配置相关片段。

// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
// 配置的key = EnableAutoConfiguration,与代码中一致
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
    将这些自动配置类的路径加载进来后,后续会根据这些自动配置类的依赖条件去加载这些类。
    Springboot特有的,常见的条件依赖注解有:
1. @ConditionalOnBean,仅在当前上下文中存在某个bean时,才会实例化这个Bean。
2. @ConditionalOnClass,某个class位于类路径上,才会实例化这个Bean。
3. @ConditionalOnExpression,当表达式为true的时候,才会实例化这个Bean。
4. @ConditionalOnMissingBean,仅在当前上下文中不存在某个bean时,才会实例化这个Bean。
5. @ConditionalOnMissingClass,某个class在类路径上不存在的时候,才会实例化这个Bean。
6. @ConditionalOnNotWebApplication,不是web应用时才会实例化这个Bean。
7. @AutoConfigureAfter,在某个bean完成自动配置后实例化这个bean。
8. @AutoConfigureBefore,在某个bean完成自动配置前实例化这个bean。
    如果你查看某个starter提供的自动配置类,那么你应该会看见不少上面这些注解。至此,自动配置的流程便完成了。但是如果你仔细思考了整个流程,我们还有一个非常重要的东西在前面没有讲述。那就是AutoConfigurationImportSelector类的selectImports()什么时候被调用,这也是许多文章所忽略的点,而这便是整个SpringBoot自动配置流程的起点。
    看过AutoConfigurationImportSelector实现的朋友不难发现,AutoConfigurationImportSelector实现了好几个接口,其中有一个接口是与自动配置相关的那便是DeferredImportSelector,而DeferredImportSelector接口是继承自ImportSelector接口的。为什么我说DeferredImportSelector这个接口是与自动配置相关的接口?接下来的知识和SpringBoot启动流程密切相关,建议读者想去了解一下SpringBoot启动流程的相关知识。

    接下来讲的知识有一定的跳跃,我不是从selectImports()讲起,而是从SpringBoot启动流程讲起。ConfigurationClassPostProcessor这个类实现了BeanDefinitionRegistryPostProcessor接口。BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的扩展, 允许在BeanFactoryPostProcessor被调用之前对BeanDefinition做一些操作, 尤其是它可以注册BeanFactoryPostProcessor的BeanDefinition. 它提供了一个方法postProcessBeanDefinitionRegistry(), 这个方法被调用的时候, 所有的BeanDefinition已经被加载了, 但是所有的Bean还没被创建 (注:BeanDefinition就是Bean的配置元数据或Bean的描述信息, 比如Bean的属性值, 构造方法的参数值等)。以下是ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法实现:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

	/**
	 * Derive further bean definitions from the configuration classes in the registry.
	 */
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);

		processConfigBeanDefinitions(registry);
	}

	
	/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		//略

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		//略
		do {
            //调用ConfigurationClassParser的parse()方法
			parser.parse(candidates);
		}while (!candidates.isEmpty());
		//略
	}
}
//ConfigurationClassParser的parse()方法如下
class ConfigurationClassParser {
	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

		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);
			}
		}
	//执行找到的 DeferredImportSelector 
	//  DeferredImportSelector 是 ImportSelector 的一个变种。
	// ImportSelector 被设计成其实和@Import注解的类同样的导入效果,但是实现 ImportSelector
	// 的类可以条件性地决定导入哪些配置。
	// DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才处理。这也正是
	// 该语句被放到本函数最后一行的原因。
		processDeferredImportSelectors();
	}

    private void processDeferredImportSelectors() {
		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
		this.deferredImportSelectors = null;
		Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

		for (DeferredImportSelectorHolder deferredImport : deferredImports) {
			ConfigurationClass configClass = deferredImport.getConfigurationClass();
			try {
                //这里便是AutoConfigurationImportSelector的selectImports的入口
				String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
				processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
		}
	}
}
    仔细阅读上面的代码,你应该就能找到selectImports()的入口,也即SpringBoot自动配置启动流程的起点。这里再提一下BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()是在哪里被调用的吧。BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法是在AbstractApplicationContext的refresh()中的invokeBeanFactoryPostProcessors(beanFactory)里面被调用的。
    相信了解过Spring或SpringBoot启动流程的朋友对AbstractApplicationContext的refresh()方法应该很熟悉了,如果不知道这个函数的话,赶紧去补一下课吧,嘿嘿^_^。这些内容就越过本文要讲述的东西了,我就不在赘述了。
    最后,在阅读本文的时候希望大家善用Eclipse的单步调试、Ctrl+H和鼠标右键的References选项。





参考资料:

Spring Boot自动化配置浅析: http://www.voidcn.com/article/p-zdchqzel-bmu.html 

深入理解SpringBoot自动装配:https://www.cnblogs.com/niechen/p/9027804.html 

SpringBoot的自动配置原理:https://segmentfault.com/a/1190000018011535

给你一份长长长的 Spring Boot 知识清单(上): https://cloud.tencent.com/developer/article/1380173

给你一份长长长的 Spring Boot 知识清单(下): https://cloud.tencent.com/developer/article/1380172

Spring的扩展点: http://loveshisong.cn/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/2016-11-22-Spring%E7%9A%84%E6%89%A9%E5%B1%95%E7%82%B9.html

Spring 工具类 ConfigurationClassParser 分析得到配置类: https://blog.csdn.net/andy_zhang2007/article/details/78549773

Spring EnableAutoConfigurationImportSelector 是如何工作的: https://blog.csdn.net/andy_zhang2007/article/details/78580980 

转载请注明:
    本文转载自:www.kantblog.com/blog/WebDevelop/8

Kant©2016 All rights reserved 粤ICP备16014517号