SpringBoot 自动装配原理
SpringBoot 基本特性
AutoConfiguration 自动装配
Starter
Actuator
SpringBoot CLI
我们为什么会使用SpringBoot?
让我们回想一下,在springboot没有出现之前,我们通常使用SSM(Spring、SpringMVC、Mybatis)架构搭建应用程序,用过Spring框架的同学知道,我们通常称为“万能胶”,因为Spring社区擅长继承各类框架;既然擅长集成各类框架,哪就免不了一些配置,在没有Annotation出现之前,全部都是xml配置,回想一下,当时我们搭建一个框架需要多少xml配置?是不是想想都头疼;在Annotation出现之后变减少了一些xml。
我们为什么会使用SpringBoot呢?当然是因为使用起来简洁、方便呀,什么简单?配置简单,拿过来一个starter,运行即可,无需额外的配置便可启动运行。
那么SpringBoot是如何做到这么简单的配置呢?让我们来看接下来的解答。
约定优于配置
在软件开发领域当中,我们或多或少听说过这个概念,其实SpringBoot就是这一概念的主要体现。
-
maven 的目录结构
默认resources文件夹存放配置文件
默认打包方式为jar
提供开箱即用starter
默认配上文件application.properties/yml
等等...
基本注解(介绍注解的主要目的是为了以后分析自动装配原理做铺垫)
SpringBoot 作为微服务的一个实现框架,其实并没有什么新的技术产生,都是依赖于Spring中原有的技术来封装的。
让我们从@SpringBootApplication注解入手来分析SpringBoot中自动装配相关注解
@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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {}
@SpringBootApplication 注解是一个复合注解,我们主要分析@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration 注解。
@ComponentScan
这个注解打架接触的最多,相当于在xml中配置context:component-scan,它的作用就是扫描指定路径下需要自动装配的类。
标识需要自动装配类的注解为:@Component、 @Repository、@Service、@Controller。
@ComponentScan 默认会扫描当前package下所有标注相关注解的类,将其注入到Spring的IoC中。
@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {}
@SpringBootConfiguration 注解其实是@Configuration注解的一个封装,@Configuration注解我们应该不会陌生,他是基于javaConfig形式的基于SpringIoC容器的配置类的一种注解。
@Configuration 注解的作用是声明一个javaConfig配置类,而配置类中的任何一个@Bean的方法,它的返回值都会作为Bean的定义注册到SpringIoc中,方法名默认就是这个bean的id。
@EnableAutoConfiguration
@EnableAutoConfiguration 注解其实也不是什么新的注解,我们应该经常看到过以@Enable开头的注解,这里不详细解释了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
@EnableAutoConfiguration 这个复合注解中有两个主要的注解,@AutoConfigurationPackage、@Import注解,这里我们先简单解释一下这两个注解作用,稍后结合SpringBoot自动装配的原理进行解释。
@Import 注解的作用就是将其他位置的javaConfig配置类导入到当前环境下,进行加载相应的Bean。
@AutoConfigurationPackage 注解下其实也是使用@Import注解。
@Import注解 和 @AutoConfiguration注解虽然在功能上作用是一样的,但是是不同的两种方式加载Bean到IoC容器中。
深入分析SpringBoot 自动装配原理
通过前面的分析我们知道,SpringBoot自动装配机制主要发生在@EnableAutoConfiguration这个注解上,那么下面我们将详细分析下这个注解。
Selector 装配方式 和 Register 装配方式
SpringBoot 中有两种自动装配的方式,一个是实现 ImportSelector接口,一个是实现 ImportBeanDefinitionRegistrar接口,我们这里只介绍ImportSelector方式,ImportBeanDefinitionRegistrar方式请读者自行查阅。
AutoConfigurationImportSelector 类(实现ImportSelector接口方式)
首先我们先看下AutoConfigurationImportSelector类结构关系和源码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 加载元数据,获取需要过滤调类的一些信息
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 加载类路径下 META-INF/spring.factories 文件和排除不需要的配置类
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
// 返回所有的配置类名称的数组
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
上面的源码就是自动配置的所有逻辑,首先通过注解获取配置注解上的元数据,例如:exclude 属性;其次通过getAutoConfigurationEntry()方法加载类路径下所有META-INFO文件夹下的spring.factories文件并进行解析,处理;最后将处理好的配置类名称以数组的形式返回。
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取注解上的元数据
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有META-INFO/spring.factories配置文件
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
// 获取所有需要排除的配置类
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 移除需要排除的配置类
configurations.removeAll(exclusions);
// 过滤掉不符合condition的配置类
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
上述的代码逻辑非常的清晰,这里就不多做介绍了,我们看下getCandidateConfigurations()这个方法。
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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;
}
这段代码就是通过SpringFactoriesLoader的loadFactoryNames()加载所有的spring.factories文件。那么spring.factories文件和我们的自动装配有什么关系呢?让我们看下面的图片
上图是spring.factories文件的一部分,其中key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration的有好多配置类的全路径,SpringBoot就是根据约定大于配置的原则,将配置类写到 key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value中,然后通过SpringFactoriesLoader.loadFactoryNames()方法进行加载,代码如下。
// 加载META-INF/spring.factories 文件的逻辑
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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;
}
// 返回EnableAutoConfiguration类型的Class
protected Class> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
// 根据class类型加载配置文件中的信息
public static List loadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 具体读取配置文件的信息
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
到此为止SpringBoot自动装配的逻辑结束,接下来就是Spring容器进行刷新后同构后置处理器进行调用后注册到SpringIoC容器中。