传统的Spring框架实现一个Web服务,需要导入各种依赖jar包,然后编写对应的XML配置文件等,相较而言,SpringBoot显得更加方便、快捷和高效。那么,SpringBoot究竟如何做到这些的呢?
接下来分别针对SpringBoot框架的依赖管理、自动配置和执行流程进行深入分析

依赖管理

问题1:为什么导入dependency时不需要指定版本?

在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖,
分别是spring-boot-starter-parent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:

1、spring-boot-starter-parent依赖

在项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下:
它是用来进行版本启动管理的,后续的都不需要进行写入版本了

  1. <!-- Spring Boot父项目依赖管理 -->
  2. <parent>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-parent</artifactId>
  5. <version>2.2.11.RELEASE</version>
  6. <relativePath/> <!-- lookup parent from repository -->
  7. </parent>

image.png
上述代码中,将spring-boot-starter-parent依赖作为SpringBoot项目的统一父项目依赖管理,并将项目版本号统一为2.2.11.RELEASE,该版本号根据实际开发需求是可以修改的

使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下
image.png
………….

spring-boot-dependencies
image.png
因为通过集成spring-boot-dependencies,从底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件已经进行了统一版本号管理,例如activemq、spring、tomcat等,都有与SpringBoot当前的版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。(一旦版本变动,依赖也会变动,避免依赖冲突) (但是只是部分依赖

需要说明的是,如果pom.xml引入的依赖文件不是spring-boot-starter-parent管理的,那么在pom.xml引入依赖文件时,需要手动的使用标签指定依赖文件的版本号。

问题2:spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的jar包是从何而来的?

image.png

(由上面的问题1,加上这个图,就可以清晰的理解,spring-boot-starter-parent是继承了很多依赖的jar,自定义的项目又引入spring-boot-starter-parent,那么自然就继承了很多依赖的jar)

1、spring-boot-starter-web依赖(maven版本)

查看spring-boot-starter-web依赖文件源码,核心代码具体如下
(利用的是maven依赖传递的特性)

1、点进去

image.png

2、里面已经进行依赖管理

在这里进行着对各个版本的管理,以及为什么不需要手动引入,也能工作的原因
image.png

3、里面的Tomcat点进去,存在Tomcat自己依赖的版本

image.png

image.png

  1. 从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖。<br /> 正是如此,**在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发**,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理。

2、spring-boot-starter-web依赖(Gradle版本)

  1. api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
  2. api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-json"))
  3. api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-tomcat"))
  4. api("org.springframework:spring-web")
  5. api("org.springframework:spring-webmvc")
  1. SpringBoot**除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖**,我们可以打开SpringBoot官方文档,搜索"Starters"关键字查询场景依赖启动器会列出了SpringBoot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时只需要在pox.xml文件中导入对应的依赖启动器即可。

具体步骤查看如下:
image.png

image.png

image.png

列出了Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时 只需要在pom.xml文件中导入对应的依赖启动器即可。

需要说明的是,SpringBoot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架MyBatis、阿里巴巴的Druid数据源等,SpringBoot官方就没有提供对应的依赖启动器。为了充分利用SpringBoot框架的优势,在SpringBoot官方没有整合这些技术框架的情况下,MyBatis、Druid等技术框架所在的开发团队主动与SpringBoot框架进行了整合,实现了各自的依赖启动器,例如mybatis-spring-boot-starter、druid-spring-boot-starter等。
我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。

自动配置(启动流程)

自动配置的概念:能够在我们添加 jar 包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目

问题1:SpringBoot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

那么思考,这个自动配置是如何完成的,它是在SpringBoot启动的过程中进行完成的。
其所谓的启动,就是执行核心启动类的main方法。

然后打开启动类,观察一下,就发现,上面添加了注解@SpringBootApplication就是核心启动类
image.png

接下来的任务是: 看@SpringBootApplication的作用,run方法的作用。
看看能不能找出自动配置的原理。

SpringBoot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication能够扫描Spring组件并自动配置SpringBoot

@SpringBootApplication :SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置 类SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用

@SpringBootApplication

下面,查看@SpringBootApplication内部源码进行分析,核心代码具体如下

1、点进去看SpringBootApplication注解

image.png

2、分析注解的源码

重点关注下面的三个注解:
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan

  1. //规定SpringBootApplication这个注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
  2. @Target(ElementType.TYPE)
  3. //表示该注解的生命周期,Runtime运行时(整个运行期间)
  4. @Retention(RetentionPolicy.RUNTIME)
  5. //表示注解可以记录在javadoc中
  6. @Documented
  7. //表示可以被子类继承该注解
  8. @Inherited
  9. //-----------------上面4个注解不是重点关注的,重点关注的是下面
  10. // 其实SpringBootApplication注解就是下面三个注解的组合
  11. @SpringBootConfiguration // 标明该类为配置类
  12. @EnableAutoConfiguration // 启动自动配置功能
  13. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  14. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  15. public @interface SpringBootApplication {
  16. /** 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型
  17. */
  18. @AliasFor(annotation = EnableAutoConfiguration.class)
  19. Class<?>[] exclude() default {};
  20. /** 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全
  21. 类名字符串数组 */
  22. @AliasFor(annotation = EnableAutoConfiguration.class)
  23. String[] excludeName() default {};
  24. /**
  25. * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
  26. * for a type-safe alternative to String-based package names.
  27. * <p>
  28. * <strong>Note:</strong> this setting is an alias for
  29. */
  30. @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  31. String[] scanBasePackages() default {};
  32. /**扫描特定的包,参数类似是Class类型数组。*/
  33. @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  34. Class<?>[] scanBasePackageClasses() default {};
  35. /**
  36. * The {@link BeanNameGenerator} class to be used for naming detected components
  37. * within the Spring container.
  38. */
  39. @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
  40. Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  41. @AliasFor(annotation = Configuration.class)
  42. boolean proxyBeanMethods() default true;
  43. }

从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面4个是注解的元数据信息,
我们主要看后面3个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下:

1.A、第一个看@SpringBootConfiguration注解

@SpringBootConfiguration注解的作用与@Configuration注解相同都是标识一个可以被组件扫描器扫描的配置类。

1、点进去看@SpringBootConfiguration

image.png

2、分析该注解源码

@SpringBootConfiguration注解表示SpringBoot配置类。
查看@SpringBootConfiguration注解源码,核心代码具体如下。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
  5. @Indexed
  6. public @interface SpringBootConfiguration {
  7. /*代理方法*/
  8. @AliasFor(annotation = Configuration.class)
  9. boolean proxyBeanMethods() default true;
  10. }

从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被SpringBoot进行了重新封装命名而已。所以SpringBootConfiguration类就是一个配置类,SpringBootApplication也标准了这个配置类,那么SpringBootApplication也称为一个配置类。

1.B、第二个看@EnableAutoConfiguration注解

@EnableAutoConfiguration注解表示开启自动配置功能,该注解是SpringBoot框架最重要的注解,也是实现自动化配置的注解

1、点进去看@EnableAutoConfiguration

image.png

2、分析该注解的源码

查看该注解内部查看源码信息,核心代码具体如下

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. // --------这里重点分析下面两个注解
  6. // 自动配置包
  7. @AutoConfigurationPackage
  8. /*Spring的底层注解@Import,给容器中导入一个组件;
  9. 导入的组件是AutoConfigurationPackages.Registrar.class*/
  10. @Import(AutoConfigurationImportSelector.class)
  11. // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
  12. public @interface EnableAutoConfiguration {
  13. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  14. /**
  15. * 返回不会被导入到 Spring 容器中的类
  16. */
  17. Class<?>[] exclude() default {};
  18. /**
  19. * 返回不会被导入到 Spring 容器中的类名
  20. */
  21. String[] excludeName() default {};
  22. }

可以发现它是一个组合注解
Spring中有很多以Enable开头的注解,其作用就是借助@lmport来收集并注册特定场景相关的bean并加载到loC容器
@EnableAutoConfiguration就是借助@lmport来收集所有符合自动配置条件的bean定义,并加载到loC容器。
那么在SpringBoot启动过程中,都会把哪些bean注册到容器中呢?

下面,对这两个核心注解分别讲解:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
具体剖析:https://www.yuque.com/jixiangkuaile/kk8w4w/vii985

1.C、@ComponentScan注解

使用该注解时,只需要配置一个扫描路径。

主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。
常用属性如下: basePackages、value:可以用来指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
basePackageClasses:指定具体扫描的类
includeFilters:指定满足Filter条件的类
excludeFilters:指定排除Filter条件的类

includeFilters和excludeFilters 的FilterType可选:ANNOTATION=注解类型 默认、 ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ类型)、REGEX(正则表达式)、CUSTOM(自定义类 型),自定义的Filter需要实现TypeFilter接口

@ComponentScan的配置如下:

  1. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes =
  2. TypeExcludeFilter.class),
  3. @Filter(type = FilterType.CUSTOM, classes =
  4. AutoConfigurationExcludeFilter.class) })

借助excludeFilters将TypeExcludeFillter及FilterType这两个类进行排除

当前@ComponentScan注解没有标注basePackages及value,所以扫描路径默认为@ComponentScan 注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication注解的项目启动类所在 的路径)

@ComponentScan注解具体扫描的包的根路径由SpringBoot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到SpringBoot项目主程序
启动类所在包的具体位置 com.slin.edu.这个包下。

总结:

@SpringBootApplication的注解的功能就分析差不多了,简单来说就是3个注解的组合注解:

抛出疑问:

@EnableAutoConfiguration注解是通过@Import注解加载了自动配置固定的bean
@ComponentScan注解自动进行注解扫描
那么上面两个配置类,是如何进行加载解析的呢?不可能放上去就能生效吧!
他们两个是何时加载解析,又是如何进行加载解析??

那么真正根据包扫描,添加了注解的组件类,何时生成实例对象存到IOC容器中,又是怎么来完成的?

其实主要是在run方法里面。