SpringBoot是对Spring的一种扩展,其中比较重要的扩展功能就是自动装配:通过注解对常用的配置做默认配置,简化xml配置内容 。本文会对Spring的自动配置的原理和部分源码进行解析,本文主要参考了Spring的官方文档 。
自动装配的组件SpringBoot自动装配通过多部分组件协调完成,这些组件主要有下面几种,这几种组件之间协调工作,最终完成了SpringBoot的自动装配 。
- @EnableAutoConfiguration:用于根据用户所引用的jar包自动装配Spring容器,比如用户在ClassPath中包含了HSQLDB,但是没有手动配置数据库连接,那么Spring会自动使用HSQLDB作为数据源 。
- @Condition:不同情况下按照条件进行装配,Spring的JdbcTemplate是不是在Classpath里面?如果是,并且DataSource也存在,就自动配置一个JdbcTemplate的Bean
- @ComponentScan:扫描指定包下面的@Component注解的组件 。
- 全手工配置的XML文件阶段,用户需要的Bean全部需要在XML文件中声明,用户手工管理全部的Bean 。
- 半手工配置的注解阶段,用户可以安装需求Enable对应的功能模块,如添加@EnableWebMvc可以启用MVC功能 。
- 全自动配置的SpringBoot,用户只需要引入对应的starter包,Spring会通过factories机制自动装配需要的模块 。

文章插图
半自动注解配置示意图:

文章插图
全自动注解配置示意图:

文章插图
Spring启用全自动配置功能的注解就是@EnableAutoConfiguration,应用添加了@EnableAutoConfiguration注解之后,会读取所有jar包下面的spring.factories文件,获取文件中配置的自动装配模块,然后去装配对应的模块 。
@EnableAutoConfiguration的功能可总结为:使Spring启用factories机制导入各个starter模块的配置 。
原理分析通过上面的分析我们知道Spring的@EnableAutoConfiguration主要功能是使Spring启用factories机制导入各个starter模块的配置 。下面我们会对@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注解的定义有两部分比较重要的内容:@AutoConfigurationPackage:将添加该注解的类所在的package作为自动配置package进行管理 。
@Import({AutoConfigurationImportSelector.class}):用于导入factories文件中的AutoConfiguration 。
@Import({AutoConfigurationImportSelector.class})首先我们需要知道@Import注解的作用,从字面意思就可以看出来,@Import用于把一个Bean注入到Spring的容器中,@Import可以导入三种类型的Bean:
- 导入普通的Bean,通常是@Configuration注解的Bean,也可以是任意的@Component组件类型的类 。
- 导入实现了ImportSelector接口的Bean,ImportSelector接口可以根据注解信息导入需要的Bean 。
- 导入实现了ImportBeanDefinitionRegistrar注解的Bean, ImportBeanDefinitionRegistrar接口可以直接向容器中注入指定的Bean 。
@Import({AutoConfigurationImportSelector.class})中的AutoConfigurationImportSelector实现了ImportSelector接口,会按照注解内容去装载需要的Bean 。public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {// 获取需要自动装配的AutoConfiguration列表AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);// 获取自动装配类的类名称列表return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}// 获取需要自动装配的AutoConfiguration列表protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {// 获取注解中的属性AnnotationAttributes attributes = this.getAttributes(annotationMetadata);// 获取所有META-INF/spring.factories中的AutoConfiguration类List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);// 删除重复的类configurations = this.removeDuplicates(configurations);// 获取注解中Execlud的类Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);// 移除所有被Exclude的类configurations.removeAll(exclusions);// 使用META-INF/spring.factories中配置的过滤器configurations = this.getConfigurationClassFilter().filter(configurations);// 广播相关的事件this.fireAutoConfigurationImportEvents(configurations, exclusions);// 返回符合条件的配置类 。return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}}@AutoConfigurationPackage@AutoConfigurationPackage用于将添加该注解的类所在的package作为自动配置package进行管理,听起来是不是和@ComponentScan功能有所重复?我们来分析一下其具体实现,可以看到这个注解依旧是通过@Import注解向容器中注册Bean 。@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import({Registrar.class})public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {};}@AutoConfigurationPackage注解导入了Registrar.class,其本质是一个ImportBeanDefinitionRegistrar,会把当前注解类所在的包注入到Spring容器中 。static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));}public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));}}@ComponentScan并不会把类所在的包注入到容器中,@ComponentScan只注入指定的包 。类所在的包通过@AutoConfigurationPackage注入 。@Conditional注解@Conditional注解的组件只有在满足特定条件的情况下才会被注册到容器中,@Conditional注解的定义如下所示,可以看到这个注解只有一个内容:Condition,所以这个注解的重点就是Condition接口 。
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Conditional {/*** All {@link Condition} classes that must {@linkplain Condition#matches match}* in order for the component to be registered.*/Class<? extends Condition>[] value();}Condition接口的定义如下所示,该接口只包含一个方法,输入当前的上下文信息和注解的参数,判断注解的Bean是否可以注册到Spring容器中 。其中上下文信息包含了:Bean定义管理器(BeanDefinitionRegistry)/BeanFactory/上下文环境Environment/资源加载器ResourceLoader/类加载器ClassLoader 。@FunctionalInterfacepublic interface Condition {/*** Determine if the condition matches.* @param context the condition context* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}* or {@link org.springframework.core.type.MethodMetadata method} being checked* @return {@code true} if the condition matches and the component can be registered,* or {@code false} to veto the annotated component's registration*/boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}@Conditional判断时机@Conditionl注解作用于Spring读取Bean定义的阶段,这个阶段大多数Bean尚未实例化,少数实例化的Bean属于Spring的特殊Bean,不能条件控制是否加载 。Spring中的Bean有很多来源,如扫描包下的Component、@Bean注解的方法、@Import、用户手工注册Bean等方法注册Bean,这些所有来源的Bean定义都可以使用@Conditional进行处理吗?答案是不是所有Bean定义来源都会使用@Conditional注解进行过滤,只有扫描包或者@Configuration注解类中的的Bean会使用@Conditionl注解进行判断 。
- 扫描包注册Bean,我们知道基于注解的Spring容器会通过扫描包的形式去获取路径下面的Component,这部分Bean定义是最早被加载到Spring容器中的 。Spring通过
AnnotatedBeanDefinitionReader读取并注册@Component对应的Bean定义,其中@Conditional判断的部分逻辑如下:
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,@Nullable BeanDefinitionCustomizer[] customizers) {AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);// 此处判断Bean是否应该被注册if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}// 省略其它注册逻辑} - @Bean方法注册,在@Configuration中的@Bean和不在@Configuration中的@Bean的模式不同,我在其它文章中有介绍,但是获取Bean定义的逻辑是一致的,@Bean注解的方法是通过
ConfigurationClassBeanDefinitionReader去读取的,其中也包含了对@Conditional注解的判断,其中部分关键判断代码如下所示 。
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {ConfigurationClass configClass = beanMethod.getConfigurationClass();MethodMetadata metadata = https://tazarkount.com/read/beanMethod.getMetadata();String methodName = metadata.getMethodName();// Do we need to mark the bean as skipped by its condition?if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {configClass.skippedBeanMethods.add(methodName);return;}if (configClass.skippedBeanMethods.contains(methodName)) {return;}@Configuration类中的@Bean注解是在ConfigurationClassPostProcessor中进行解析和注册的,这是一个BeanDefinitionRegistryPostProcessor,执行时机显然晚于扫描包的时机 。
@Conditional扩展为了简化用户的使用,Spring提供了几种常见的@Conditional的实现,我们下文中会介绍常见的几种实现 。
- @ConditionalOnBean注解,当特定的Bean注解存在的时候注册当前Bean,使用这个注解的时候要注解Bean的先后顺序,因为@ConditionalOnBean会去当前容器中查找是否有满足条件的Bean,后注册的Bean会受先注册Bean的影响 。
- @ConditionalOnClass注解,当前类路径中包含指定类的时候注册当前Bean 。
- @ConditionalOnMissingBean注解,当指定类型的Bean不存在的时候注册当前Bean 。
- @ConditionalOnMissingClass注解,当前类路径中不包含指定类的时候注册当前Bean 。
- @ConditionalOnProperty注解,指定属性满足特定值是生效 。
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
@ComponentScan组件有些情况下,我们的组件不都定义在带有@EnableAutoConfiguration注解的类对应的包下面,这个时候@AutoConfigurationPackage扫描的类就不能满足用户的需求了 。Spring提供了@ComponentScan组件让用户添加指定包下面的Spring组件到Spring容器中 。
Filter选项扫描包过程中,Spring允许用户按照条件过滤所需要的Bean,@SpringBootApplication中本身包含了
@ComponentScan注解,并为注解配置了两个Filter:TypeExcludeFilter和AutoConfigurationExcludeFilter注解 。这两个注解允许用户实现特定的类,从而实现对Bean定义的过滤 。@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 {// 省略属性}@Import原理通过上面的内容,我们知道@Import在Spring的自动装配中有很重要的作用,用于自动装配过程中导入指定的配置类 。接下来我们分析一下@Import注解的源码及其作用机制 。@Import的解析依旧是在关键类
ConfigurationClassPostProcessor中进行的,ConfigurationClassPostProcessor包含了@Bean、@Import等注解的解析 。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));}// 其它逻辑}
文章插图
【springboot面试题 SpringBoot自动装配】本文最先发布至微信公众号,版权所有,禁止转载!
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
