1 简介
在Spring Boot的学习中难免需要接触源码,而入手及时从Spring Boot项目启动类开始入手。项目启动类非常简单,仅仅存在一个注解@SpringBootApplication以及一个运行参数为被该注解标注类run函数。
@SpringBootApplication
public class BiuApplication {
public static void main(String[] args) {
SpringApplication.run(BiuApplication.class, args);
}
}
对于该启动类的分析,就从这个Spring Boot的核心注解开始入手。
2 核心注解@SpringBootApplication
字面分析,这个注解是标注一个Spring Boot应用。
@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 {
...//具体参数暂时忽略
}
进入到这个注解后,可以发现该注解由四个元注解,以及其他三个注解组成分别是:@SpringBootConfiguration
、
@EnableAutoConfiguration
(主要分析)、
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
2.1 Spring的配置类@SpringBootConfiguration
字面分析,这是一个Spring Boot的配置类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {...}
从他的源码来看,除了元注解之外,它仅仅被@Configuration
注解所标注,那么可以理解@SpringBootConfiguration
为他仅仅就是一个配置类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {...}
再分析@Configuration
的源码,该注解为Spring中的配置类注解,其中被@Component
标注为Spring组件,意味着他被注册到IOC容器。
因此,套用官方文档的答案:@SpringBootConfiguration
表示一个类提供 Spring Boot 应用程序@Configuration
。可以用作 Spring 标准@Configuration
注释的替代方法,以便可以自动找到配置(例如在测试中)。
2.2 开启自动配置@EnableAutoConfiguration*
这个注解是Spring Boot的自动装配的核心。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}
除了四个元注解,这个注解被两个注解所标注:
@AutoConfigurationPackage
、
@Import(AutoConfigurationImportSelector.class)
那么我们先直接往下分析:
2.2.1 自动配置包@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {...}
从注解来看,@AutoConfigurationPackage
中使用注解@Import
(@Import:的作用)导入了AutoConfigurationPackages.Registrar.class到容器中,那么来分析这个类,进入到这个内部类Regisrar:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
该类引入的重点在于方法**registerBeanDefinitions()
:**
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
首先先分析方法体中所调用的方法register()
的第二个参数
PackageImports(metadata).getPackageNames().toArray(new String[0])
进入到类PackageImports的构造方法:
PackageImports(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
this.packageNames = Collections.unmodifiableList(packageNames);
}
在这个构造方法中将元数据即启动类AnnotationMetadata metadata
经过处理
- 获取标签注解信息,注解信息里面的 basePackages 和 basePackageClasses是否有数据。
- basePackages、 basePackageClasses为注解
@AutoConfigurationPackage
中的属性。
- 如果没有数据则获取注解所在的类的名字目录,放到List中
获得packageNames属性也就是启动类所在的包。
回到Registrar中的registerBeanDefinitions()方法中register()方法的第二个参数即为启动类所在的包的名称,并且使用数组来进行表示。
在分析**register()
**方法,register()源码如下:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
这个方法的if语句为判断registry这个参数中是否已经注册了AutoConfigurationPackages的类路径所对应的bean(AutoConfigurationPackages)。如若已经被注册,则把上面分析的第二个参数所获取的包(启动类所在的包的名称)添加到这个bean的定义中。如若没有,则注册这个bean并且把包名设置到该bean的定义中。
小结:@AutoConfigurationPackage就是添加该注解的类所在的包作为自动配置包进行管理。他的实现就是依赖于工具类AutoConfigurationPackages中的内部类Registrar对所标注的包进行注册。
2.2.2 导入 自动配置导入选择器@Import(AutoConfigurationImportSelector.class)
这个@import
使用了2.2.1中@import的用法中的3.3 通过ImportSelector 方式导入的类,所以我们进入到该类,直接找到selectImports方法。在这个用法中,所返回的字符串数组为所有的将要被导入的类的全类名。那么知道这个方法是做什么的,就开始分析这个方法。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
从return开始分析,autoConfigurationEntry自动配置实体中List的属性configurations将被返回。autoConfigurationEntry是通过方法**getAutoConfigurationEntry()
**获得的,那么就进入到这个方法
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);
}
根据return所返回的内容,返回的是一个使用属性configurations所生成的自动配置实体,configurations是使用
**getCandidateConfigurations()
**获取候选配置所得到的。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> 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()
进行获取的。那么就继续进入loadFactoryNames()
:
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());
}
在这个方法中classLoaderToUse是一个关键变量,在这个方法中通过所传进来的参数获得,如果为空则直接获取SpringFactoriesLoader的ClassLoader
。然后将第二个参数factoryType的类名传入变量factoryTypeName,最后放入**loadSpringFactories()
**,那么我们还需要知道这个方法实现了什么:
//这个方法返回的为Map<String, List<String>> 变量result。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//从全局变量cache中以classLoader为key获取所对应的value
Map<String, List<String>> result = cache.get(classLoader);
//判断在cache中是否存在相对应的value,如果已经存在则直接返回对应的result
if (result != null) {
return result;
}
//至此,已经判断得出result为空,所以实例化一个新的HashMap
result = new HashMap<>();
try {
//从传入的类加载器中获取资源,路径为"META-INF/spring.factories"
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
//将获取到的urls进行遍历
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//获取资源的url在当前项目中的位置等
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());
//遍历当前配置实体中的所有value,如果不存在当前key,则将当前key以及下面遍历所得到的value一起添加到result中,如果存在则将该value添加到所对应的key下面。
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)));
//将该classLoader以及对应的result添加到cache中
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
//至此,返回了META-INF/spring.factories中的所有的配置
return result;
}
这样我们开始从最后,一步步带上参数分析
------------------------------------------>
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
{
...
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
//已经得知loadSpringFactories根据一个类加载器获得了所有的配置文件,所获得的所有配置进行getOrDefault()处理,如果存在与factoryTypeName相对应的List则返回,如果没有则获取一个空List,由loadSpringFactories可知只要classLoader不为空,则可获取所有配置文件。因此可以判断返回了foryType类名所对应的所有配置
-------------------------------------->
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes){
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
...
return configurations;
}
//再来分析参数一getSpringFactoriesLoaderFactoryClass()
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
//获取到了类EnableAutoConfiguration。参数二就是当前类中的全局变量beanClassLoader。
//通过这两个参数可以获得配置文件中key为EnableAutoConfiguration的所有value。因此可以判断当前方法返回的是以List的配置文件中所有的自动配置的类。
---------------------------------->
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
{
...
return new AutoConfigurationEntry(configurations, exclusions);
}
//这个方法是将获取的所有自动配置类进行去重、排除、过滤等一系列的操作然后返回处理完成后的配置,并将其包装为AutoConfigurationEntry
------------------------------>
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata)
{
...
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//最后回到梦开始的地方,这一步是将所有的配置类转成字符串数组,然后通过@Import的使用方法————将数组内的所有配置导入到容器中。
小结:@EnableAutoConfiguration的实现方式是导入配置文件META-INF/spring.factories中EnableAutoConfiguration所对应的所有的类。
2.3组件扫描@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ComponentScan 中使用的是过滤排除的规则,此处不再进行详细解读。