您的位置:首页 > 编程语言 > Java开发

SpringBoot自动配置原理

2020-02-11 19:10 330 查看

一、前言

这是我写的第一篇博客,以前学习都是直接做成md文件的笔记的,今后想改变下学习方式。我知道我这篇博客写的其实并不怎么好,但这也是我第一次将所学知识输出出去,感觉这种学习方式还不错,同时还望大佬们指教。

二、回顾

用过Spring的都知道,Spring给我们提供了两大特性,IoC和AOP,IoC依赖控制反转,AOP面向切面编程。通过这两大特性能够解决开发遇到的复杂度问题。

然而Spring使用起来却相当的不方便,需要我们写大量配置文件,不能做到开箱即用。

SpringBoot因此应运而生,将配置文件以Java代码的方式,写入Spring容器中,以达到开箱即用的特性。

三、什么是自动配置

SpringBoot的核心就是自动配置,通过编写Java代码方式(以下称JavaBean)将大量配置注入到Spring容器里。相关源码都在spring-boot-autoconfigure包里。

以往我们引入一个新的框架时,我们需要在resources目录配置相应的xxx.xml文件来提供框架配置信息。然后每新建一个新的项目,如果用到相同的框架,也要重复相同的步骤,将提前写好的xxx.xml文件复制到resources目录下。这就很不科学啊。我们是什么?我们是程序员啊,重复的操作就应该想办法让他能够复用。于是SpringBoot就来了。

SpringBoot通过将各种配置文件xxx.xml转化成xxxAutoConfiguration类的方式,将大量常用配置以JavaBean的方式注入到Spring容器里,以达到只要少量配置甚至不用配置即可直接使用,实现了约定大于配置的原则,让我们能更加专注于业务本身,而不是配置。

四、自动配置是怎么开启的

相信聪明的你应该已经注意到你们创建的SpringBoot相比常规的Spring项目多了个入口类和注解。

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

@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) })
public @interface SpringBootApplication {

虽然使用了比较多的注解,但我们还是能一眼看出我们的主角

@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

通过源码,我们可以看出@EnableAutoConfiguration是一个组合注解,由@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)这两个注解组成。

@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

这个注解通过Spring的底层注解@Import,给容器导入一个组件AutoConfigurationPackages.Registrar.class,通过这个组件将入口类(@SpringBootApplication标注的类)的所在包及其下面所有子包里面的所有组件扫描到Spring容器里。

AutoConfigurationImportSelector.class

这个类看名字就知道,它是用来选择的,选择什么呢?选择导入到Spring容器的自动配置类,将所有需要导入的类添加进容器中,其中就包含了非常多的配置类。

/**
*	注意:当前的SpringBoot版本是2.2.1.RELEASE,因为我发现和2.0.x版本不一致,但差别不多,就此说明一下
*/
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);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

其中最关键的就是selectImports方法,在2.2.x版本后(我也不知道具体是哪个版本后的改动,有兴趣的可以自行查找)封装成AutoConfigurationEntry实体类。


// 2.2.1版本
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

很明显,configurations里的数据就是配置类了,通过getCandidateConfigurations()获取。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
// 略
}

最后会看到SpringFactoriesLoader类。

public final class SpringFactoriesLoader {

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 略

看到它定义了个常量factories_resource_location,指向了一个配置文件


看到这里,应该明白,前面getCandidateConfigurations()方法,就是通过读取META-INF/spring.factories配置文件,来获取所有AutoConfiguration全类名,进行注入操作。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// loadSpringFactories方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}

try {
Enumeration<URL> 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);
}
}

五、总结

  • 入口类加上@SpringBootApplication
  • @EnableAutoConfiguration:开启自动配置
  • @AutoConfigurationPackage: @Import(AutoConfigurationPackages.Registrar.class),AutoConfigurationPackages.Registrar.class用于扫描引用了该注解的全类名及以下的子包,并将其中用Spring注解(@Service等)标注过的类(组件)注入到Spring容器里。这个组件就是为什么我们加了@Service后,Service类就注入到Spring容器的原因。
  • @Import(AutoConfigurationImportSelector.class):
      AutoConfigurationImportSelector.class,通过SpringFactoriesLoader.loadFactoryNames()读取META-INF/spring.factories配置文件,从而获取所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径,并通过这些全路径将它们注入到Spring容器中。
    • 点赞
    • 收藏
    • 分享
    • 文章举报
    qq813997065 发布了1 篇原创文章 · 获赞 0 · 访问量 30 私信 关注
  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: