SpringBoot自动配置

SpringBoot提供了自动配置功能,这样可以让我们更加专注的做我们关注的业务逻辑,而不必过多的去考虑哪些在使用Spring的时候需要考虑的各种配置,例如我们在使用spring webmvc的时候,提供下边这样一段代码,然而,这段代码对于所有使用Spring webMvc而言,基本属于通用,也就是我们添加一个有关webmvc的模块就需要提供这样一段代码,所以springboot有一次做了封装,提供了一种自动配置特性,我们只需要提供功能实现所依赖的jar包,这些功能就可以自动配置到容器中,那么自动配置的到底是怎么实现的呢?

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

首先我们看一下通常我们使用SpringBoot时,入口类上的注解@SpringBootApplication,使用这个注解类标识着当前应用为springboot应用,而这个注解上又标注了一个非常重要的注解@EnableAutoConfiguration,这个注解标注着当前应用允许自动配置,那么它到底是怎么实现的,接着看这个注解会发现导入了一个类AutoConfigurationImportSelector.class,没错,这个类是关键,他是我们进行自动配置一把钥匙。

接下来我们看看SpringBoot是怎么利用这个类实现自动配置的,关于这个一点,我们需要结合SpringBoot与spring的源码一起说明,跟踪springboot应用启动流程,在容器refresh()方法调用的时候,会调用parser.parse(candidates);这样一段代码,查看spring源码的时候也看过,这个方法会循环解析ConfigClass配置类,并且扫描指定包下的所有类,并且把一些符合添加的类转换为BeanDefinition,那这个地方和springboot的自动配置,又与刚才提到的AutoConfigurationImportSelector.class有什么关系;我们接着往里看。

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

在parse()方法中,我们看到this.deferredImportSelectorHandler.process();这样一个方法调用,这就是处理自动配置的核心调用了,但是需要主要的是,这里并不是全部,只是一个入口而言,自动有导入与剔除两部分,现在我们讲导入部分,关于剔除后边在说。

接着说什么是导入,springBoot提供了一个非常全面的“配置表”,只要我们提供了这个配置表中所需要的依赖时,对应的配置项就会配置到容器中。

继续往process()方法中查看,如下代码,我们会发现有grouping.getImports()调用,这个方法就是拿到所有auto-configutation

		public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

查看spring源码需要耐心,getImports()方法调用看上去什么都没有做,其实它只是一个引子方法,真正工作的还是别的类;

接着看this.group.process(xx)方法,进去我们会发现跳转到AutoConfigurationImportSelector.class,这就是我们最开始提到的哪个类,现在知道为什么要在@SpringBootApplication中import这个类了吧。看看具体实现,大体看描述,我们应该能猜到,这个方法在拿取自动配置实例,具体怎么拿的,在什么地方拿?getAutoConfigurationMetadata()方法调用就是具体拿取所有自动配置实例的方法?

		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

接着看这个方法的实现,你会发现这个地方使用了缓存,但是这个不是我们要关心的,缓存只是为了提供效率,我们现在研究的是原理,所以需要接着看AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);这段代码的实现

		/**
		 * 这个地方太重要了,用于加载类Spi中的自动配置类信息
		 * @return
		 */
		private AutoConfigurationMetadata getAutoConfigurationMetadata() {
			if (this.autoConfigurationMetadata == null) {
				this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
			}
			return this.autoConfigurationMetadata;
		}

到这里算是找到了目的地了,继续在深入会发现这里其实就是调用了文件加载,读取配置文件中的内容,然后将这些配置信息转换为auto-configuration,而这个文件路径为:“META-INF/spring-autoconfigure-metadata.properties”,但是需要注意的是,还有一个文件我们需要读取;

AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
      .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

刚才我们看了getAutoConfigurationMetadata()这个方法,接着我们看getAutoConfigurationEntry()方法, 这个方法中的getCandidateConfigurations()方法调用会实现读取spring.factories文件,而这个文件中也包含很多auto-configuration

而在后边我们看到有一个filter()方法调用,这就是刚才提到的剔除操作

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//取出所有的auto-configuration类
		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);//根据OnWebApplicationCondtion, OnBeanCondition, OnClassCondtion条件进行排除
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

其实有人说这是一个类SPI协议实现,就是指定一个路径,然后再容器启动的时候去读取该文件的内容,而springboot也正式利用这一点,并在此基础进行拓展实现了自动配置功能,还是需要注意的是,自动配置到此并没有完,还只是开始,我们只是找到需要自动配置的哪些配置类了,后续还有很多工作,包括刚才提到的剔除工作

	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

这里我们看看META-INF/spring-autoconfigure-metadata.properties内容,这里只取其中一部分

#Sun Jan 12 16:46:59 CST 2020
org.springframework.boot.actuate.autoconfigure.ldap.LdapHealthContributorAutoConfiguration.ConditionalOnClass=org.springframework.ldap.core.LdapOperations
org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration.AutoConfigureAfter=org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration,org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration.ConditionalOnClass=io.micrometer.core.instrument.binder.tomcat.TomcatMetrics,org.apache.catalina.Manager
org.springframework.boot.actuate.autoconfigure.metrics.export.graphite.GraphiteMetricsExportAutoConfiguration.AutoConfigureBefore=org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration,org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration
org.springframework.boot.actuate.autoconfigure.security.servlet.SecurityRequestMatchersManagementContextConfiguration=
org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointReactiveWebExtensionConfiguration.ConditionalOnWebApplication=REACTIVE
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraReactiveHealthContributorAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraOperations

到这里,springboot提供的需要自动配置的类已经被转换成configClasses了,而刚才说到spring refresh()方法的parse()会循环对这些类进行解析,验证,最后哪些被扫描到的类都会被转换为BeanDefintion,当这些类被转换为beanDefinition后,后续操作就是我们所熟悉了。

其实这里很感慨,spring真是太厉害了,提供了一个核心,然后公共各种类似切片的方式可以拓展各种各样的功能,着灵活性真是太强大了

其实说到这里,关于SpringBoot的自动配置还没有讲完,上述类SPI实现方法有局限,难道我们只能自动配置Springboot这两个配置内中提供的这些功能么,如果我们自己写了一个插件包,需要与SpringBoot进行结合,难道就没有办法自动配置了么,那么SpringCloud又是怎么实现这种自动配置,插拔式功能的?

关于SpringBoot starter这样插拔是功能,我们后边再做分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值