天天看点

SpringBoot 源码分析

传统的spring框架实现web服务需要导入很多依赖,并编写对应的xml配置文件,而springboot可以进行依赖管理和自动配置

1. 依赖管理:以web项目为例,有两个核心依赖

  • 为什么导入dependency时不需要指定版本?
  • spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行的jar包从何而来?
    • spring-boot-starter-parent依赖:作为springboot项目的统一父项目依赖管理,并将当前项目版本号统一为2.2.2RELEASE,该版本号可以很具实际开发需求修改
<!-- Spring Boot父项目依赖管理 -->
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>      
      • spring-bootstarter-parent的底层有一个父依赖spring-boot-dependencies
      <!-- 对依赖版本进行了管理-->
      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-dependencies</artifactId>
          <version>2.5.3</version>
        </parent>
            
      <!--对项目编码,java版本以及资源进行管理和过滤-->
      <properties>
          <java.version>1.8</java.version>
          <resource.delimiter>@</resource.delimiter>
          <maven.compiler.source>${java.version}</maven.compiler.source>
          <maven.compiler.target>${java.version}</maven.compiler.target>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>      
      • spring-boot-dependencies底层源文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.2.2版本相匹配的版本
      SpringBoot 源码分析
      • 如果pom.xml引入的依赖文件不是 spring-boot-starter-parent管理的,那么在pom.xml引入依赖文件时需要使用标签制定依赖文件的版本号
    • spring-boot-starter-web:spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖
<!-- web 依赖--> 
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>      
      • 在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而不要额外导入Tomcat服务器以及其他Web依赖文件等
      SpringBoot 源码分析
      • 这些依赖文件的版本号由parent父依赖进行统一管理
  • starter依赖启动器:包含了相关组件的一系列依赖文件,依赖启动器适用于不同的场景开发,使用时只需要在pox.xml文件中导入对应的依赖启动器即可
    • https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE/spring-boot-project/spring-boot-starters
    • https://mvnrepository.com/search?q=starter
  • Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架MyBatis、阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。
    • 为了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,MyBatis、Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如

      mybatis-spring-boot-starter、druid-spring-boot-starter等。

    • 我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号

2. 自动配置:自动导入组件的相关配置

  • Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
    • Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}      
      • @SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类,运行这个类的main方法启动springboot应用
    • 在SpringBootApplication 注解下
@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes =
AutoConfigurationExcludeFilter.class) }) //对组件进行过滤和扫描
public @interface SpringBootApplication {
// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组。
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 指定扫描包,参数是包名的字符串数组。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};      

// 扫描特定的包,参数类似是Class类型数组。 @AliasFor(annotation = ComponentScan.class, attribute ="basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }

  • @SpringBootConfiguration注解: SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot的配置类。
    • @SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。
    • @SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    @Indexed
    public @interface SpringBootConfiguration {
        @AliasFor(
            annotation = Configuration.class
        )
        boolean proxyBeanMethods() default true;
    }      
  • @EnableAutoConfiguration注解:开启自动配置功能
    • 是一个组合注解, Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import来收集并注册特定场景相关的 Bean ,并加载到 IOC 容器
    • @EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
    // 自动配置包
    @AutoConfigurationPackage
    
    // Spring的底层注解@Import,给容器中导入一个组件;
    // 导入的组件是AutoConfigurationPackages.Registrar.class
    @Import(AutoConfigurationImportSelector.class)
    
    // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
    public @interface EnableAutoConfiguration {
    
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};
    
    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};
    }      
      • @AutoConfigurationPackage注解:由@Import注解实现的,它是spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,registerBeanDefinitions()方法是导入组件类的具体实现
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Inherited
        @Import({Registrar.class}) // 导入Registrar中注册的组件
        public @interface AutoConfigurationPackage {
        }      
        SpringBoot 源码分析
        在Registrar类中有一个registerBeanDefinitions()方法,使用Debug模式启动项目,可以看到选中的部分就是二级包,也就是说,@AutoConfigurationPackage注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中,因此 在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发
      • @Import({AutoConfigurationImportSelector.class})注解
        • AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中,通过selectImports这个方法告诉springboot都需要导入那些组件

        

SpringBoot 源码分析
        • loadMetadata方法
SpringBoot 源码分析
        • AutoConfigurationImportSelector类 getAutoConfigurationEntry方法
protected AutoConfigurationEntry
getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
//判断EnabledAutoConfiguration注解有没有开启,默认开启
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} 
//获得注解的属性信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取默认支持的自动配置类列表
List<String> configurations =
getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = removeDuplicates(configurations);
//去除一些多余的配置类,根据EnabledAutoConfiguratio的exclusions属性进行排除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//根据pom文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类
configurations = filter(configurations, autoConfigurationMetadata);
//触发自动配置导入监听事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}      
        • getCandidateConfigurations方法的loadFactoryNames方法让SpringFactoryLoader去加载一些组件的名字
SpringBoot 源码分析
        • 继续点开loadFactory方法
      public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable
      ClassLoader classLoader) {
      //获取出入的键
      String factoryClassName = factoryClass.getName();
      return
      (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
      Collections.emptyList());
      } p
      rivate static Map<String, List<String>> loadSpringFactories(@Nullable
      ClassLoader classLoader) {
      MultiValueMap<String, String> result =
      (MultiValueMap)cache.get(classLoader);
      if (result != null) {
      return result;
      } else {
      try {
      //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
      Enumeration<URL> urls = classLoader != null ?
      classLoader.getResources("META-INF/spring.factories") :
      ClassLoader.getSystemResources("META-INF/spring.factories");
      LinkedMultiValueMap result = new LinkedMultiValueMap();
      //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
      while(urls.hasMoreElements()) {
      URL url = (URL)urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties =
      PropertiesLoaderUtils.loadProperties(resource);
      Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {
      Entry<?, ?> entry = (Entry)var6.next();
      String factoryClassName =
      ((String)entry.getKey()).trim();
      String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
      int var10 = var9.length;
      for(int var11 = 0; var11 < var10; ++var11) {
      String factoryName = var9[var11];
      result.add(factoryClassName, factoryName.trim());
      }
      }
      } 
      cache.put(classLoader, result);
      return result;      

  loadFactory方法会去读取一个 spring.factories 的文件,读取不到会标错,最终的路径是spring提供的一个工具类

public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "METAINF/spring.factories";
}      

  通过加载一个外部的文件,而这文件是在

SpringBoot 源码分析
    • 总结springboot底层实现自动配置的步骤是
      • springboot应用启动
      • @SpringBootApplication起作用
      • @EnableAutoConfiguration
      • @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的

        容器中

      • @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程

        中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的METAINF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程

  •  @ComponentScan注解:具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项目主

    程序启动类所在包的具体位置

    |- @SpringBootConfiguration
        |- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
    |- @EnableAutoConfiguration
        |- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
        |- @Import(AutoConfigurationImportSelector.class) //到METAINF/spring.factories中定义的bean添加到IOC容器中
    |- @ComponentScan //包扫描