前言
自动装配是SpringBoot的核心功能之一,我们只需要通过少量配置引入相关依赖,程序所需要的组件便会注入到Spring容器中。例如,在Spring中我们想使用Mybatis框架的话,需要在XML文件中手动配置DataSource数据源、SqlSessionFactory会话工厂、Mapper标签等,但如果使用SpringBoot,我们只需要引入以下依赖即可。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
当引入此依赖的时候,SpringBoo的自动装配会找到其对应jar包下的META-INF的spring.factories文件,将其里面的配置类全限定类路径(KEY=org.springframework.boot.autoconfigure.EnableAutoConfiguration)提取出来,交给SpringBoot的自动装配功能进一步处理,在这里会引入MybatisAutoConfiguration配置类,该配置类后期会帮我们去创建DataSource、SqlSessonFactory、MapperScannerConfigurer(用于扫描@Mapper的dao层)等Bean对象。
1.如何开启SpringBoot的自动装配 ?
SpringBoot是通过@EnableAutoConfiguration开启自动装配的,该注解需要导入spring-boot-autoconfigure依赖,不过我们这里导入更全面的spring-boot-starter,它包含了autoconfigure。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
让我们看看SpringBoot启动类上的@SpringBootApplication注解,它是一个由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解组成的组合注解。
Spring中Bean对象的产生不是凭空而来的,而是以BeanDefinition对象为模板,根据其重要的某些属性去创建的,这其中最重要的属性就是BeanDefinition的Class属性,因为Spring创建Bean对象的底层实现就是反射,所以需要类的信息。
也就是说,如果想让程序帮我们自动装配一些Bean注入到Spring容器中,那么我们肯定需要先找到候选类的信息,找到以后才能派生为BeanDefinition对象并创建为Bean对象。所以@EnableAutoConfiguration帮我们做的事情之一就是找到候选类的信息。
1.1自动装配——寻找候选类
那么,它是怎么寻找候选类的信息呢?其实是通过@EnableAutoConfiguration的子注解@Import(AutoConfigurationImportSelector.class)导入的这个类去完成寻找操作的。
从下面类图中看到,AutoConfigurationImportSelector实现了DeferredImportSelector接口,这个DeferredImportSelector又起到什么作用呢?它的中文译名为延迟导入选择器,没错它起到的作用就是等SpringBoot寻找到所有候选类的操作完成后,这个AutoConfigurationImportSelector对象才会派上用场。
寻找候选类是在AutoConfigurationImportSelector#getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)方法完成的。
具体的调用链路如下
public class AutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware...{
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadataannotationMetadata){
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//提取@EnableAutoConfiguration的exclude和excludeName的值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取所有jar包下的spring.factories文件中自动配置的类路径名称
List<String>configurations=getCandidateConfigurations(annotationMetadata,attributes);
//移除重复类名
configurations = removeDuplicates(configurations);
//获取需要排除的类名
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//校验类名是否合法
checkExcludedClasses(configurations, exclusions);
//移除排除的类名
configurations.removeAll(exclusions);
//获取Condition条件对象再次过滤不符合条件的类
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
1.1.1判断是否开启自动装配
在上图和伪代码可以看到,它主要做了好几件事,先是判断SpringBoot是否开启自动装配,默认是打开自动装配的,如果我们不希望开启自动装配功能,可以在配置文件中配置spring.boot.enableautoconfiguration=false。
1.1.2寻找所有候选类的全限定类名
而寻找候选类的全限定类名具体则是在getCandidateConfigurations方法中完成的,它最终会进入SpringFactoriesLoader#loadSpringFactories(ClassLoader classLoader)方法,在这里可以看到通过类加载器会获取项目引入的所有jar包下路径为META-INF/spring.factories的配置类中KEY=org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定路径名。
1.1.3排除无效候选类
当所有jar下spring.factories中的自动配置类的路径名都被找到后,还需要进行过滤操作将重复的类名或者一些不需要导入的类或者不满足条件的排除掉。
首先是排除指定的类,它通过@EnableAutoConfiguration的exclude、excludeName数组以及配置文件中的spring.autoconfigure.exclude属性去配置。
接着,将会获取到三个条件过滤器OnClassCondition、OnWebApplicationCondition、OnBeanCondition调用其父类FilteringSpringBootCondition的match(...)方法判断剩下的候选类是否符合一定条件保留下来。那么它是怎么判断是否符合条件的呢?
debug进去后发现,原来是在父类FilteringSpringBootCondition的match(...)方法里,真正判断是否满足条件与否是通过各个子类重写的getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata)方法执行的。不过这里并不会深入讲解各个子类重写的getOutcomes(...)方法,将列举自动装配过程涉及到的各类条件注解。
OnClassCondition
ConditionalOnClass 当指定的类存在时,保留候选类
ConditionalOnMissingClass 当指定的类不存在时,保留候选类
OnWebApplicationCondition
ConditionalOnWebApplication 当前是web环境时,保留候选类
ConditionalOnWebApplication 当前不是web环境时,保留候选类
OnBeanCondition
ConditionalOnBean 当指定的Bean存在时,保留候选类
ConditionalOnMissingBean 当指定的Bean不存在时,保留候选类
ConditionalOnSingleCandidate 当指定的Bean在容器中是单例时,保留候选类
经过各种筛选后,从132个候选类淘汰了许多,最终剩下20个符合条件的候选类。所以现在知道了为什么前文中提到的AutoConfigurationImportSelector实现了DeferredImportSelector(延迟导入选择器)的原因正是由于自动装配的配置类需要满足一定条件才允许被加载。例如ConditionalOnClass、ConditionalOnMissingClass的条件,如果SpringBoot没先把所有候选类找到,那么也就无法判断当前路径下是否存在需要的Class。
1.1.4将有效候选类&手动指定排除类封装为AutoConfigurationEntry
当前面将jar包中的META-INF/spring.factories的自动配置的配置类路径名都找出来并通过筛选后,会将剩余有效的候选类集合和手动指定排除的类集合封装为一个AutoConfigurationEntry对象。
那么为什么需要封装为AutoConfigurationEntry对象呢?因为对于SpringBoot来说,除了启动类上的@SpringBootApplication注解的@EnableAutoConfiguration会导入AutoConfigurationImportSelector这个候选类,我们也可以同样在其他地方使用@EnableAutoConfiguration,并可能地指定exclude和excludeName集合,那么此时的延迟导入选择器则是有n个。最终也会生成n个AutoConfigurationEntyr对象,而无论怎样,手动方式指定的排除类都是共享的,即如果一个@EnableAutoConfiguration中可能导入了候选类A,在另一个@EnableAutoConfiguration(exclude={A.class})排除了A,那么最终A也会被剔除出候选集合。
1.1.5获取所有有效候选类
当所有有效候选类和手动指定排除的候选类找到以后,将通过AutoConfigurationImportSelector#selectImports()方法再做一遍交叉筛选获取最终有效的候选类。
2.总结
经过上面的分析,其实SpringBoot的自动装配是通过@EnableAutoConfiguration实现的,它帮我们做的第一件事情,便是利用SpringFactoriesLoader读取引入的各个Jar包中的META-INF/spring.factories文件中KEY=org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置类路径名。
SpringBoot的自动装配,对于引入第三方组件来说是一大利器,只要遵循了规范,在其META-INF/spring.factories文件中定义好配置类Bean的全限定路径,自动装配便会逐一加载有效的候选类路径出来供给后面SpringBoot派生为BeanDefinition对象并创建为Bean对象。其实也就是说,@EnableAutoConfiguration只是帮我们将符合条件的配置类名找了出来,但是派生为BeanDefinition并进一步创建为Bean对象又是通过谁、在哪里来处理的呢?这个内容将在另外一篇文章解答。