深度剖析SpringBoot自动配置机制

目录

@SpringBootApplication

@ComponentScan

@SpringBootConfiguration

@EnableAutoConfiguration

@AutoConfigurationPackage

@Import

AutoConfigurationPackages.Registrar

@Import(AutoConfigurationImportSelector.class)

SPI机制

SpringBoot条件注解


         SpringBoot是Spring的扩展,它的主要目标是简化Spring应用的搭建和开发,通过提供“约定优于配置”的理念极大的减少了项目的配置文件和代码量。SpringBoot的配置体系强大而复杂,其中最核心的就是自动配置机制。接下来我们将以@SpringBootApplication注解为入口,深度剖析自动配置机制。

@SpringBootApplication

        @SpringBootApplication作用于程序主启动类上,使用方式很简单,如下所示:

@SpringBootApplication
public class Server {
    public static void main(String[] args) {
        SpringApplication.run(Server.class, args);
    }
}

        相较一般的注解而言,该注解比较复杂,先看它的几个重要参数:

  • exclude和excludeName用来配置一些我们不需要SpringBoot自动装配的类;
  • scanBasePackages和scanBasePackageClasses用来配置一些需要被扫描的包路径和类路径。
@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 {
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

        @SpringBootApplication是典型的组合注解,我们需要重点看的是以下三个元注解:@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration。

@ComponentScan

        @ComponentScan用来自动扫描我们所指定的一组包路径下被@Component、@Service、@Repository 和@Controller 等注解标记的类,并将其注册到Spring容器内。

Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    String resourcePattern() default "**/*.class";
    boolean useDefaultFilters() default true;
    Filter[] includeFilters() default {};
    Filter[] excludeFilters() default {};
    boolean lazyInit() default false;
	……

@SpringBootConfiguration

        @SpringBootConfiguration的元注解是@Configuration,@Configuration用于声明某个类是配置类(预告一下,我们之后会详细讲解Spring解析配置类的实现原理,敬请期待)。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

        @EnableAutoConfiguration用来开启SpringBoot自动配置功能,我们重点看它上面的两个元注解——@AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class)。

@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
}

        @AutoConfigurationPackage用来表示我们可以对该注解所在包下的类进行自动配置,该注解被@Import(AutoConfigurationPackages.Registrar.class)所标注。

@Import

        这里我们先来简单说说@Import的作用(Spring关于@Import的解析也是Spring解析配置类其中的一环),通过设置@Import的value属性,我们可以动态的向Spring容器中导入bean,并根据引入的类进行相应的处理,具体来说:

  • 如果该类实现了ImportSelector接口,Spring容器就会实例化该类,并且调用其selectImports()方法完成类的导入;
  • 如果该类实现了DeferredImportSelector接口,那么Spring将会在本轮配置解析完成之后再调用selectImports()方法;
  • 如果该类实现了ImportBeanDefinitionRegistrar接口,Spring也会在本轮配置解析完成之后去调用它的registerBeanDefinitions()方法;
  • 如果该类没有实现上述三种接口中的任何一个,那么Spring容器就会直接实例化该类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

        @AutoConfigurationPackage所导入的类是AutoConfigurationPackages.Registrar,该类有什么作用呢?

AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
	}

	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImports(metadata));
	}
}

        可以看到Registrar实现的是我们上文所提到的ImportBeanDefinitionRegistrar接口,因此我们重点看它的registerBeanDefinitions()方法。

        registerBeanDefinitions方法调用的是register()方法,该方法会将当前项目主类所在包及其子包注册为自动配置包,之后SpringBoot会在这些包下寻找自动配置类,并根据条件化注册机制来判断是否需要将其注册到Spring容器。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	if (registry.containsBeanDefinition(BEAN)) {
		BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
		beanDefinition.addBasePackages(packageNames);
	}
	else {
		registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
	}
}
@Import(AutoConfigurationImportSelector.class)

        AutoConfigurationImportSelector同样也是通过@Import导入到Spring中的,它实现了DeferredImportSelector接口,因此我们重点看它的seletImports()方法,该方法的主要作用是根据类路径上的条件和配置,动态的、选择性的向Spring导入自动配置类。

public class AutoConfigurationImportSelector implements 
	DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

        具体来说,seletImports()方法会通过SpringFactoriesLoader,并以EnableAutoConfiguration类的全限定名为key,从META-INF/spring.factories文件中找出对应value值,也就是所有自动配置类的类名,然后对这些类名进行去重。这里涉及到了一个非常重要的组件——SpringFactoriesLoader,它是Spring实现SPI机制的核心。

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;
}
configurations = removeDuplicates(configurations);
protected final <T> List<T> removeDuplicates(List<T> list) {
	return new ArrayList<>(new LinkedHashSet<>(list));
}

        去重之后,再根据@EnableAutoConfiguration注解的exclude属性或者spring.autoconfigure.exclude配置的自动配置类名字来排除对应的自动配置类。

Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);

        然后利用ConfigurationClassFilter对自动配置类做近一步的条件判断。什么意思呢?我们知道Spring Boot默认提供了100多个自动配置类,我们不可能全部引入,因此在自动配置过程时,Spring Boot会按照条件注解所定义的条件来判断是否需要加载某个自动配置类。条件注解我们会在下面详细讲解,先回到主线。

configurations = getConfigurationClassFilter().filter(configurations);

        ConfigurationClassFilter是基于AutoConfigurationMetadata实现的,AutoConfigurationMetadata对象对应的是META-INF/spring-autoconfigure-metadata.properties文件中的内容,该文件是Spring在编译阶段通过扫描classpath下的 META-INF/spring.factories 文件,获取到所有的自动配置类,然后反射这些类中的相关属性来生成相应的元数据信息(例如条件注解中所定义的一些自动配置条件),这些元数据信息最终就会被保存在spring-autoconfigure-metadata.properties文件中(如下图所示)。

        因此,Spring Boot 此时就可以根据这些元数据文件中的信息来对自动配置类进行条件判断,看其是否符合自动配置条件,如果符合条件就将该自动配置类返回给Spring容器,继续进行其他的条件判断(如@ConditionalOnMissingBean等)。

List<String> filter(List<String> configurations) {
			long startTime = System.nanoTime();
			String[] candidates = StringUtils.toStringArray(configurations);
			boolean skipped = false;
			for (AutoConfigurationImportFilter filter : this.filters) {
				boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
				for (int i = 0; i < match.length; i++) {
					if (!match[i]) {
						candidates[i] = null;
						skipped = true;
					}
				}
			}
			if (!skipped) {
				return configurations;
			}
			List<String> result = new ArrayList<>(candidates.length);
			for (String candidate : candidates) {
				if (candidate != null) {
					result.add(candidate);
				}
			}
			if (logger.isTraceEnabled()) {
				int numberFiltered = configurations.size() - result.size();
				logger.trace("Filtered " + numberFiltered + " auto configuration class in "
						+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
			}
			return result;
		}
	}

        需要注意的是,SpringBoot这里条件判断调用是AutoConfigurationImportFilter接口的match()方法,而不是Condition接口的matches()方法,这里所调用的match()方法是由FilteringSpringBootCondition实现的,而matches()方法则是由FilteringSpringBootCondition的父类SpringBootCondition实现的,我们在下面会有提到。

        这里之所以调用的是AutoConfigurationImportFilter接口的match()方法,是因为在SpringBoot自动配置过程中,通常会通过判断多种条件来确定是否加载某个自动配置类,与Condition接口的matches()方法相比,AutoConfigurationImportFilter的match方法可以支持复杂的条件组合,并且内部通过缓存之前的条件判断结果来减少重复条件的检查,提高了整体的配置效率,而Condition接口的matches方法只提供了一种基础的条件评估方式。

@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
	ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
	ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
	boolean[] match = new boolean[outcomes.length];
	for (int i = 0; i < outcomes.length; i++) {
		match[i] = (outcomes[i] == null || outcomes[i].isMatch());
		if (!match[i] && outcomes[i] != null) {
			logOutcome(autoConfigurationClasses[i], outcomes[i]);
			if (report != null) {
				report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
			}
		}
	}
	return match;
}

        通过这种机制SpringBoot可以灵活的按需加载自动配置类,有效避免了扫描、加载、解析大量的配置信息,从而使得SpringBoot整个自动配置以及容器初始化过程更加轻量级和高效。

SPI机制

        在面向对象的设计里,模块之间一般都是基于接口编程,不会对实现类进行硬编码,因为这样违反了可拔插的原则,一旦遇到需要替换实现类的情况,就需要手动修改代码。因此就需要一种服务发现机制来进行解耦。

        SPI的全称是Service Provider Interface,是一种供第三方实现或扩展的服务发现机制,本质上是基于接口化编程+策略模式+配置文件实现的动态加载机制。

        Java SPI规定,当服务提供者提供了接口的具体实现后,需要在jar包的META-INF/services目录下创建一个文件,该文件需要以接口的全限定名来命名,文件内容是该接口实现类的全限定名,并且改jar包需要在主程序的classpath路径下,这样主程序就可以通过ServiceLoader来动态加载这个实现类,其底层基于Java反射机制实现。

        和Java SPI不同,Spring SPI的配置文件是一个固定名称的文件——META-INF/spring.factories,spring.factories支持K-V结构,所有接口的实现类都可以放在该文件中,多个实现类之间用逗号分割,避免了创建一大堆配置文件,通过SpringFactoriesLoader我们可以按需加载指定接口的实现类,其底层也是基于Java反射机制实现。

SpringBoot条件注解

        基于Sping的@Conditional注解,SpringBoot提供了一系列的条件注解,可以让我们控制bean在满足某种条件时才加载。这些条件注解可以用在配置类、自动配置类以及普通的类上,通过设置不同的条件,可以灵活地控制Spring Boot中的配置和自动配置行为。

        从上图可以看到,@ConditionalOn系列的条件注解有很多多,我们不对所有注解展开讲解,因为这些注解的实现原理大同小异,我们重点看下@ConditionalOnClass。

        @ConditionalOnClass上面有个@Conditional(OnClassCondition.class),我们重点看下OnClassCondition.class,其中就包含了@ConditionalOnClass的实现原理。

        OnClassCondition类继承自FilteringSpringBootCondition,FilteringSpringBootCondition类又继承了SpringBootCondition,SpringBootCondition是Condition接口的实现类,Condition接口的matches()方法也是在SpringBootCondition中实现的,简单讲就是先获取当前解析的类名或方法名用于之后记录条件匹配结果,然后调用getMatchOutcome()方法来进行条件匹配。

@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			……
		}
	}

        SpringBootCondition中的getMatchOutcome()方法是一个抽象方法,其具体条件匹配的实现逻辑我们回到OnClassCondition类来看。

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	ClassLoader classLoader = context.getClassLoader();
	ConditionMessage matchMessage = ConditionMessage.empty();
	List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
	if (onClasses != null) {
		List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
		if (!missing.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
					.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
		}
		matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
				.found("required class", "required classes")
				.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
	}
	
	List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
	if (onMissingClasses != null) {
		List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
		if (!present.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
					.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
		}
		matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
				.didNotFind("unwanted class", "unwanted classes")
				.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
	}
	return ConditionOutcome.match(matchMessage);
}

        该方法的具体逻辑是,如果类或方法上有@ConditionalOnClass注解,就获取@ConditionalOnClass注解中的value属性,也就是要判断是否存在的一组类的名称,然后利用ClassNameFilter.MISSING来判断这些类有没有被找到,如果没有找到就将这些类的名称存入missing集合。如果最后missing集合不为空,就表示存在没有被找到的类,此时条件不匹配,SpringBoot会利用ConditionMessage将缺少的类记录下来,然后return。

        由于@ConditionalOnClass注解和@ConditionalOnMissingClass注解的实现逻辑是非常类似,因此SpringBoot将它们的实现逻辑都放在了OnClassCondition一个类中,所以在getMatchOutcome()方法中,可以同时处理这两个注解的逻辑,当然@ConditionalOnMissingClass我们就不赘述了。


欢迎关注微信订阅号:技术勘察馆

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值