SpringBoot自动配置原理
一、前言
这是我写的第一篇博客,以前学习都是直接做成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容器的原因。
-
AutoConfigurationImportSelector.class,通过SpringFactoriesLoader.loadFactoryNames()读取META-INF/spring.factories配置文件,从而获取所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径,并通过这些全路径将它们注入到Spring容器中。
- 点赞
- 收藏
- 分享
- 文章举报
- [Spring Boot] 4. Spring Boot实现自动配置的原理
- SpringBoot自动配置原理
- Spring Boot 自动配置原理
- Spring Boot之自动配置的原理
- Spring Boot自动配置原理与实践(一)
- SpringBoot自动配置的实现原理
- Spring boot实战中自动配置原理分析
- Spring Boot核心原理-自动配置
- SpringBoot学习 (四) - 自动配置原理 静态资源的映射规则 模板引擎 SpringMVC自动配置-扩展-全面接管
- springboot学习--EnableAutoConfiguration自动配置的原理
- SpringBoot学习之自动配置原理
- SpringBoot_自动配置原理
- Spring Boot核心原理-自动配置
- spring boot自动配置原理
- SpringBoot学习笔记(一)SpringBoot2 自动配置原理
- 全面解析SpringBoot自动配置的实现原理
- SpringBoot自动配置原理-学习
- Spring Boot自动配置原理
- SpringBoot(4)—自动配置原理
- 【Spring Boot 系列 自动配置原理分析】