分享

SpringBoot源码解析(一)

 码农9527 2021-07-01

 不知道大家在第一次搭建SpringBoot项目时是否有注意到,没有繁琐的配置文件,引入依赖时也不需要指定版本。整合第三方框架,如果需要开启相应的功能,只需要在启动类上添加@Enablexxxx注解即可实现效果。项目启动更是简单,只需运行启动类中的main方法,我们的SpringBoot项目就跑起来了。

SpringBoot源码解析

    版本依赖

    先回答第一个问题,SpringBoot是如何做到不指定版本也能找到合适的版本号。

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.2.2.RELEASE</version>
 <relativePath/>
</parent>123456复制代码类型:[java]

    打开pom文件,我们可以看到有个parent标签,这时候我们按住ctrl+鼠标左键点击spring-boot-starter-parent,会看到以下内容。

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-dependencies</artifactId>
 <version>2.2.2.RELEASE</version>
 <relativePath>../../spring-boot-dependencies</relativePath>
</parent>123456复制代码类型:[java]

    我们发现,该文件中并没有关于版本号的踪迹。莫慌,它还有个parent,继续按住ctrl+鼠标左键点击spring-boot-dependencies进入,找到properties标签,这里定义了一系列的版本。

<properties>
 <activemq.version>5.15.11</activemq.version>
 <antlr2.version>2.7.7</antlr2.version>
 <appengine-sdk.version>1.9.77</appengine-sdk.version>
 <artemis.version>2.10.1</artemis.version> // 此处省略若干版本号...
 <wsdl4j.version>1.6.3</wsdl4j.version>
 <xml-maven-plugin.version>1.0.2</xml-maven-plugin.version>
 <xmlunit2.version>2.6.3</xmlunit2.version>
</properties>12345678910复制代码类型:[java]

    至此,第一个问题我们就解答完毕。

    自动配置原理

package com.javafamily.familydemo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class FamilyDemoApplication { public static void main(String[] args) {
  SpringApplication.run(FamilyDemoApplication.class, args);
 }

}12345678910111213复制代码类型:[java]

    一个最简单的SpringBoot项目其实只有一个Application启动类。我们想要研究自动配置,能够点击的也就只有@SpringBootApplication注解和run方法了。

    今天咱们先看@SpringBootApplication注解,话不多说,直接开干。

    点击@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 { // 内容省略...}12345678910111213复制代码类型:[java]

    我们发现,@SpringBootApplication是一个组合注解。

    前四个是专门(即只能)用于对注解进行注解的,称为元注解。

    @Target

    用来定义注解标记的位置(如类、方法、属性等)

    @Retention

    注解的声明周期(源文件、编译期、运行时保留)

    @Documented

    用于标记在生成javadoc时是否将注解包含进去

    @Inherited

    子类继承父类的注解(FamilyDemoApplication的子类将自动继承@SpringBootApplication注解)

    @SpringBootConfiguration:

    进入@SpringBootConfiguration,我们发现它被@Configuration标记,也是一个组合注解。

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration { @AliasFor(
  annotation = Configuration.class
 )
 boolean proxyBeanMethods() default true;
}12345678910复制代码类型:[java]

    继续点击@Configuration,又看到一个熟悉的注解@Component。

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface Configuration { @AliasFor(
  annotation = Component.class
 )
 String value() default ""; boolean proxyBeanMethods() default true;
}123456789101112复制代码类型:[java]

    至此,我们可以得出两个结论:

    该注解与@Configuration注解功能相同,仅表示当前类为一个JavaConfig类,区别是前者为SpringBoot的注解,后者是SpringFramework的注解。

    @Configuration本质上还是@Component,@ComponentScan可以处理@Configuration注解的类。

    @ComponentScan:

    默认扫描当前类所在的包以及子孙包。需要注意的是配置扫描工作并不是由该注解完整,真正的工作者还是@EnableAutoConfiguration。

// 扫描指定的包ComponentScan.Filter[] includeFilters() default {};// 排除不需要扫描的包ComponentScan.Filter[] excludeFilters() default {};12345复制代码类型:[java]

    @EnableAutoConfiguration:

    该注解用于开启自动配置,也是一个组合注解。所谓自动配置指的是会自动找到所需的类(SpringBoot&开发人员定义),然后交给Spring容器完成这些类的装配。

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { // 内容省略...}1234567891011复制代码类型:[java]

    @AutoConfigurationPackage:

    将@SpringBootApplication标记的类所在包及其子孙包中所有组件都扫描到Spring容器里。

    @Import:

    用来导入所需配置类或需要前置加载的类。

    到这里,我们就只剩下AutoConfigurationImportSelector还没看。该类主要关注以下两个方法。

/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 * @param autoConfigurationMetadata the auto-configuration metadata
 * @param annotationMetadata the annotation metadata of the configuration class
 * @return the auto-configurations that should be imported
 */protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
  AnnotationMetadata annotationMetadata) { // 判断自动配置是否开启
 if (!isEnabled(annotationMetadata)) {  return EMPTY_ENTRY;
 } // 获取注解的属性
 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 通过SPI机制从META‐INF/spring.factories中获得需要自动配置的类
 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 依靠LinkedHashSe机制移除重复配置类
 configurations = removeDuplicates(configurations); // 根据@EnableAutoConfiguration注解中的属性,获取不需要加载的配置类集合
 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 校验不需要加载的配置类是否合法
 checkExcludedClasses(configurations, exclusions); // 从configurations移除不需要加载的类
 configurations.removeAll(exclusions); // 过滤不符合条件的配置类
 configurations = filter(configurations, autoConfigurationMetadata); // 触发自动配置导入时间
 fireAutoConfigurationImportEvents(configurations, exclusions); // 创建AutoConfigurationEntry对象
 return new AutoConfigurationEntry(configurations, exclusions);
}1234567891011121314151617181920212223242526272829303132复制代码类型:[java]
/**
 * Return the auto-configuration class names that should be considered. By default
 * this method will load candidates using {@link SpringFactoriesLoader} with
 * {@link #getSpringFactoriesLoaderFactoryClass()}.
 * @param metadata the source metadata
 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
 * attributes}
 * @return a list of candidate configurations
 */protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 通过SPI机制从META-INF/spring.factories加载类型为EnableAutoConfiguration类
 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;
}1234567891011121314151617复制代码类型:[java]

    getCandidateConfigurations方法中其实就一行核心代码,我们继续跟进。

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(); // 调用loadSpringFactories方法
 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 {  // classLoader加载的FACTORIES_RESOURCE_LOCATION已经在类中定义了 值为META-INF/spring.factories
  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);
 }
}123456789101112131415161718192021222324252627282930313233343536373839复制代码类型:[java]

    loadFactoryNames方法会调用loadSpringFactories方法,而在loadSpringFactories方法中会读取本类中FACTORIES_RESOURCE_LOCATION变量。这个变量的值正是META-INF/spring.factories。

    那么META-INF/spring.factories文件在哪找呢?

    举个例子:项目中引入了MyBatisPlus依赖,按照下图指示即可找到相应的配置文件。其它依赖也是如此。

Java

    最后,附上注解依赖图结束本次的源码探险之旅,我们下次再见。

Java教程

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多