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经过处理

  1. 获取标签注解信息,注解信息里面的 basePackages 和 basePackageClasses是否有数据。
  • basePackages、 basePackageClasses为注解@AutoConfigurationPackage中的属性。
  1. 如果没有数据则获取注解所在的类的名字目录,放到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 中使用的是过滤排除的规则,此处不再进行详细解读。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐