文章目录
- 前言
- 1、main入口
- 2、@SpringBootApplication
- 3、@EnableAutoConfiguration
- 4、AutoConfigurationImportSelector
- 4.1、selectImports()
- 4.2、getAutoConfigurationEntry()
- 4.3、getCandidateConfigurations()
- 4.4、loadFactoryNames()
- 5、META-INF/spring.factories
- 6、总结
前言
早期的Spring
项目需要添加需要配置繁琐的xml
,比如MVC
、事务、数据库连接等繁琐的配置。Spring Boot
的出现就无需这些繁琐的配置,因为Spring Boot
基于约定大于配置的理念,在项目启动时候,将约定的配置类自动装配到IOC
容器里。
这些都因为Spring Boot
有自动装配的特性。
接下来将会逐步从源码跟踪进去,一步步掀开自动装配的面纱。
1、main入口
在SpringBoot项目的启动类中,注解SpringBootApplication
是必须需要添加的,既然是从这里启动的,那么自动装配的操作应该也是在启动的时候去执行的吧,我们一步步挖进去一探究竟。
@SpringBootApplication
public class SpringbootInitApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootInitApplication.class, args);
}
}
2、@SpringBootApplication
点进来@SpringBootApplication
注解之后会发现,这玩意头顶怎么挂着这么多注解的。不要着急,与Bean注入也只有三个相关,而自动装配的核心注解也是只有一个:
@SpringBootConfiguration
:继承自Spring的@Configuration
注解,作用也大致相同,支持在入口处通过@Bean
等注解手动配置一下 Bean 加入到容器中;@EnableAutoConfiguration
:顾名思义,这玩意就是用来自动装配的,都写在人家脸上了,接下来主要的介绍核心也是该注解;@ComponentScan
:告诉Spring需要扫描哪些包或类,如果不设值的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,这也是为什么放置启动类位置有要求的原因。
@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 {
// 省略该注解内属性和方法
// ......
}
3、@EnableAutoConfiguration
点进来@EnableAutoConfiguration
注解,去掉那些有的没的注解,可以初步发现与自动装配有关的应该是有两个,分别是注解@AutoConfigurationPackage
和导入的类AutoConfigurationImportSelector
。
点进去注解@AutoConfigurationPackage
发现里面没有什么有用的信息,其作用是将添加该注解的类所在的package 作为自动装配 package 进行管理,感兴趣的小伙伴可以自行点进去查看。
AutoConfigurationImportSelector
作为被导入的类,也是实现自动装配的核心类之一,接下来将会点进去查看其细节。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 省略该注解内属性和方法
// ......
}
4、AutoConfigurationImportSelector
4.1、selectImports()
在AutoConfigurationImportSelector
类中,自动装配核心方法为selectImports()
,在其文档中是这么介绍该方法的:
Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.
Returns: the class names, or an empty array if none
根据
@Configuration
中的AnnotationMetadata
,选择并返回应该被导入的类的名称。返回:类名,如果不存在则返回空数组
可以看到从这里便已经获取被设置需要自动装配的类的信息了,从源码中可以看到在selectImports()
中的核心方法应该是getAutoConfigurationEntry()
。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// 省略类中成员属性...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// 省略类中其余方法...
}
4.2、getAutoConfigurationEntry()
getAutoConfigurationEntry()
方法同样处于AutoConfigurationImportSelector
类中,在刚方法中主要返回的是已经配置好的配置数组,该配置数组被包装在类中,其中AutoConfigurationImportSelector.AutoConfigurationEntry
的介绍如下:
Create an entry with the configurations that were contributed and their exclusions.
Params: configurations – the configurations that should be imported exclusions – the exclusions that were applied to the original list
使用所提供的配置及其排除项创建一个条目。
参数:configurations ——应该导入的配置 exclusions – 应用于原始列表的排除项
protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
// 封装应考虑的自动配置类名称和被排除的数据
return new AutoConfigurationEntry(configurations, exclusions);
}
4.3、getCandidateConfigurations()
方法getCandidateConfigurations()
同样处于AutoConfigurationImportSelector
类中,该方法主要用于获取应考虑的自动装配类名称,而获取候选项的方法为其中的loadFactoryNames()
方法。
Return the auto-configuration class names that should be considered. By default this method will load candidates using ImportCandidates with getSpringFactoriesLoaderFactoryClass().
返回应考虑的自动配置类名称。默认情况下,此方法将使用ImportCandidates和getSpringFactoriesLoaderFactoryClass()加载候选项。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
// 获取候选项
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
4.4、loadFactoryNames()
挖到这里其实就差不多到头了,再往下就不太礼貌了。
在这里干的活在官方给出的文档中都说的很简单明了了:
Load the fully qualified class names of factory implementations of the given type from “META-INF/spring.factories”, using the given class loader.
As of Spring Framework 5.3, if a particular implementation class name is discovered more than once for the given factory type, duplicates will be ignored.
使用给定的类加载器,从"META-INF/spring.factories"加载给定类型的工厂实现的完全限定类名。
从 Spring Framework 5.3 开始,如果针对给定工厂类型多次发现特定实现类名称,则将忽略重复项。
从介绍中就可以看到所谓的自动装配,就是将META-INF/spring.factories
中写明白的内容给加载出来,同时连上面提及到的排除项都写明白在这里了,而源码也是对官方文档进行直接翻译了,猜测是出于封装性和代码简洁度考虑,开发人员将核心逻辑封装成了私有方法loadSpringFactories()
:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
// 核心流程
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 获取资源,常量FACTORIES_RESOURCE_LOCATION的值为META-INF/spring.factories
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
// 逐一处理加载到的资源
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();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
5、META-INF/spring.factories
spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 里面默认配置了很多aoto-configuration,如下:
以WebMvcAutoConfiguration
为例:
package org.springframework.boot.autoconfigure.web.servlet;
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(HttpPutFormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
return new OrderedHttpPutFormContentFilter();
}
......etc
}
这里有一个地方不知道大家有没有注意到,有大部分的注解前面都有Conditional
字样的,这其实也是SpringBoot
的强大之处之一,其使用了 Spring 4 框架的新特性:@Conditional
注释,此注解使得只有在特定条件满足时才启用一些配置。这也 Spring Boot “智能” 的关键注解。Conditional大家族如下:
注解 | 描述 |
---|---|
@ConditionalOnBean | 当容器中存在指定的Bean时生效 |
@ConditionalOnClass | 当类路径中存在指定的类时生效 |
@ConditionalOnExpression | 通过SpEL表达式指定条件,满足条件时生效 |
@ConditionalOnMissingBean | 当容器中不存在指定的Bean时生效 |
@ConditionalOnMissingClass | 当类路径中不存在指定的类时生效 |
@ConditionalOnNotWebApplication | 当应用程序不是Web应用时生效 |
@ConditionalOnResource | 当指定的资源存在于类路径中时生效 |
@ConditionalOnWebApplication | 当应用程序是Web应用时生效 |
6、总结
SpringBoot自动配置原理如下:
@EnableAutoConfiguration
注解导入AutoConfigurationImportSelector
类;- 执行
selectImports()
方法调用SpringFactoriesLoader.loadFactoryNames()
扫描所有jar
下面的对应的META-INF/spring.factories
文件; - 限定为
@EnableAutoConfiguration
对应的value
,将这些装配条件的装配到IOC
容器中。
自动装配简单来说就是自动将第三方的组件的 bean
装载到 IOC
容器内,不需要再去写 bean
相关的配置,符合约定大于配置理念。SpringBoot 基于约定大于配置的理念,配置如果没有额外的配置的话,就给按照默认的配置使用约定的默认值,按照约定配置到 IOC
容器中,无需开发人员手动添加配置,加快开发效率。
同时,对于自己开发SDK时,也可利用 SpringBoot 自动装配原理,编写自己的 META-INF/spring.factories
文件,从而将某些特定需求的类生成 Bean
放入容器中。