浅析Spring boot自动装配原理
什么是 spring boot
springboot 框架是为了能够帮助使用 spring 框架的开发者快速高效的构建一个基于 spirng 框架以及 spring 生态体系的应用解决方案。它是对“约定优于配置”这个理念下的一个最佳实践。因此它是一个服务于框架的框架,服务的范围是简化配置文件。
约定优于配置的体现
- maven 的目录结构
a) 默认有 resources 文件夹存放配置文件
b) 默认打包方式为 jar - spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的 tomcat 容器,使得构建一个 web 应用更加简单
- 默认提供 application.properties/yml 文件
- 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件
- EnableAutoConfiguration 默认对于依赖的 starter 进行自动装载
从@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}
)}
)
SpringBootApplication 本质上是由 3 个注解组成,分别是:
- @SpringBootConfiguration --实际上就是@Configuration
- @EnableAutoConfiguration
- @ComponentScan
简单分析@SpringBootConfiguration
它是 JavaConfig形式的基于 Spring IOC 容器的配置类使用的一种注解。任何一个标注了@Configuration 的 Java 类定义都是一个JavaConfig 配置类。而在这个配置类中,任何标注了@Bean 的方法,它的返回值都会作为 Bean 定义注册到Spring 的 IOC 容器,方法名默认成为这个 bean 的 id。
简单分析@ComponentScan
它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到 spring 的 Ioc 容器中。
标识需 要装配的类的 形式主要是: @Component 、@Repository、@Service、@Controller 这类的注解标识的类。
ComponentScan 默认会扫描当前 package 下的的所有加了相关注解标识的类到 IoC 容器中;
简单分析@EnableAutoConfiguration
@EnableAutoConfiguration 的 主 要 作 用 其 实 就 是 帮 助springboot 应用把所有符合条件的@Configuration配置都加载到当前 SpringBoot 创建并使用的 IoC 容器中。其中最重要的就是@Import注解,看下源码:
@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 {};
}
Import 注解
import 注解是什么意思呢? 联想到 xml 形式下有一个 < import resource/> 形式的注解,就明白它的作用了。import 就是把多个容器配置合并在一个配置中。在JavaConfig 中所表达的意义是一样的。
深入分析 EnableAutoConfiguration
EnableAutoConfiguration 的 主 要 作 用 其 实 就 是 帮 助spring boot 应用把所有符合条件的@Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器中。再回到 EnableAutoConfiguration 这个注解中,我们发现它的 import 是这样的:
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration{
@Import 注解可以配置三种不同的 class:
- 第一种就是基于普通 bean 或者带有@Configuration 的 bean 进行注入
- 实现 ImportSelector 接口进行动态注入
- 实现 ImportBeanDefinitionRegistrar 接口进行动态注入
@EnableAutoConfiguration 注解的实现原理
它会通过 import 导入第三方提供的 bean 的配置类:AutoConfigurationImportSelector
从名字来看,可以猜到它是基于 ImportSelector 来实现基于动态 bean 的加载功能。
定位到AutoConfigurationImportSelector 这个类中的selectImports 方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
selectImports 返回的数组(类的全类名)都会被纳入到spring 容器中。
本质上来说,其实 EnableAutoConfiguration 会帮助springboot 应用把所有符合@Configuration 配置都加载到当前 SpringBoot 创建的 IoC 容器,而这里面借助了Spring 框架提供的一个工具类 SpringFactoriesLoader 的支持。以及用到了 Spring 提供的条件注解@Conditional,选择性的针对需要加载的 bean 进行条件过滤。
SpringFactoriesLoader
这里简单分析一下SpringFactoriesLoader 这个工具类的使用。它其实和java 中的 SPI 机制的原理是一样的,不过它比 SPI 更好的点在于不会一次性加载所有的类,而是根据 key 进行加载。
SpringFactoriesLoader 的作用是从classpath/META-INF/spring.factories文件中,根据 key 来加载对应的类到 spring IoC 容器中。
条件过滤
在分析 AutoConfigurationImportSelector 的源码时,会先扫描 spring-autoconfiguration-metadata.properties文件,最后在扫描 spring.factories 对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration 其实是依托于其他的框架来加载的,如果当前的 classpath 环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration 类的数量从而降低SpringBoot 的启动时间。
Conditional 中的其他注解
Conditions | 描述 |
---|---|
@ConditionalOnBean | 在存在某个 bean 的时候 |
@ConditionalOnMissingBean | 不存在某个 bean 的时候 |
@ConditionalOnClass | 当前 classpath 可以找到某个类型的类时 |
@ConditionalOnMissingClass | 当前 classpath 不可以找到某个类型的类时 |
@ConditionalOnResource | 当前 classpath 是否存在某个资源文件 |
@ConditionalOnProperty | 当前 jvm 是否包含某个系统属性为某个值 |
@ConditionalOnWebApplication | 当前 spring context 是否是 web 应用程序 |