springboot原理初探

ゞ 浴缸里的玫瑰 2024-04-06 10:11 199阅读 0赞

前言

  • 本文是作者的入门笔记,学习狂射说,其中的主要知识,借鉴自狂神的课堂笔记。
  • 本文章仅供学习和参考使用,没有任何商业的用途。如果对原作者造成损失,希望大家及时通知作者,我一定会及时处理。
  • 虽然,学习的是老的课程,但是本质还是不变的,然后我是用的是比较新的东西,在组织博客,相当于再学习一遍

文章目录

  • 前言
  • 运行原理探究
    • 起源·pom.xml
    • 启动器 ·spring-boot-starter
    • 主启动类·SpringbootApplication
      • 注解·@SpringBootApplication
      • 注解·@ComponentScan
      • 注解·@SpringBootConfiguration
      • 注解·@EnableAutoConfiguration
      • 注解·@Import({AutoConfigurationImportSelector.class})
      • 遗憾·疑问spring.factories源码【看不到大量的自动装配源码】
      • 结论
      • 不平凡方法·SpringApplication
        • SpringApplication.run分析

运行原理探究

起源·pom.xml

父依赖:主要是管理项目的资源过滤及插件

  1. <!-- 父依赖 -->
  2. <!-- lookup parent from repository -->
  3. <parent>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-parent</artifactId>
  6. <version>2.7.3</version>
  7. <relativePath/>
  8. </parent>

点进spring-boot-starter-parent,发现还有一个父依赖

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-dependencies</artifactId>
  4. <version>2.7.3</version>
  5. </parent>

点进spring-boot-dependencies:真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心

  1. <groupId>org.springframework.boot</groupId>
  2. <artifactId>spring-boot-dependencies</artifactId>
  3. <version>2.7.3</version>

在这里插入图片描述

  • 结论

    • 导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了。

启动器 ·spring-boot-starter

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  • springboot-boot-starter-xxx:就是spring-boot的场景启动器
  • spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件。

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 用什么功能就导入什么样的场景启动器即可

主启动类·SpringbootApplication

默认的主启动类

  1. //@SpringBootApplication 来标注一个主程序类 , 说明这是一个Spring Boot应用
  2. @SpringBootApplication
  3. public class SpringbootApplication {
  4. public static void main(String[] args) {
  5. //启动了一个方法,其实启动了一个服务
  6. SpringApplication.run(SpringbootApplication.class, args);
  7. }
  8. }

注解·@SpringBootApplication

  • 作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就可以运行这个类的main方法来启动SpringBoot应用

点进@SpringBootApplication:可以看到如下

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = {
  8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  10. public @interface SpringBootApplication {
  11. //······
  12. }

注解·@ComponentScan

  • 这个注解在Spring中很重要 ,它对应XML配置中的元素。
  • 作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
  1. @ComponentScan(param)告诉Spring 哪个packages 的用注解标识的类 会被spring自动扫描并且装入bean容器,param即用来指定扫描包的范围。
  2. 此注解一般和@Configuration注解一起使用,指定Spring扫描注解的package。如果没有指定包,那么默认会扫描此配置类所在的package。

    • @Configuration注解申明当前类是一个配置类,相当于xml配置文件。
    • @ComponentScan和@Configuration一起使用的原因就是基于Spring2.0中的注解配合xml配置文件的实现一样,即在xml配置文件配置ComponentScan包扫描属性。

注解·@SpringBootConfiguration

  • 作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类

点进@SpringBootConfiguration

  1. @Target({
  2. ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Configuration
  6. @Indexed
  7. public @interface SpringBootConfiguration {
  8. @AliasFor(
  9. annotation = Configuration.class
  10. )
  11. boolean proxyBeanMethods() default true;
  12. }
  • @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件
  • @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用

注解·@EnableAutoConfiguration

  • @EnableAutoConfiguration :开启自动配置功能
  • SpringBoot可以自动帮我们配置 ;*** @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,自动配置才能生效***

点进@EnableAutoConfiguration

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @Import(AutoConfigurationPackages.Registrar.class)
  6. public @interface AutoConfigurationPackage {
  7. //······
  8. }
  • @AutoConfigurationPackage : 自动配置包
  • @import :Spring底层注解@import , 给容器中导入一个组件
  • Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器

注解·@Import({AutoConfigurationImportSelector.class})

  • 作用:给容器导入组件
  • @Import({AutoConfigurationImportSelector.class}) :给容器导入组件

点进AutoConfigurationImportSelector.class

  • CTRL+F在本源文件中快速查找
  • 可以找到以下代码【181–189行】

    // 获得候选的配置
    protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

    1. //这里的getSpringFactoriesLoaderFactoryClass()方法
    2. //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    3. List<String> configurations = new ArrayList<>(
    4. SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    5. ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    6. Assert.notEmpty(configurations,
    7. "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
    8. + "are using a custom packaging, make sure that file is correct.");
    9. return configurations;
    10. }

这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法【126–1333行】

  1. public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  2. ClassLoader classLoaderToUse = classLoader;
  3. if (classLoaderToUse == null) {
  4. classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  5. }
  6. String factoryTypeName = factoryType.getName();
  7. //这里它又调用了 loadSpringFactories 方法
  8. return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
  9. }

点进 loadSpringFactories【135–169行】

  1. private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
  2. //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
  3. Map<String, List<String>> result = cache.get(classLoader);
  4. if (result != null) {
  5. return result;
  6. }
  7. result = new HashMap<>();
  8. try {
  9. /*
  10. 去获取一个资源 "META-INF/spring.factories"
  11. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  12. */
  13. Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
  14. //将读取到的资源遍历,封装成为一个Properties
  15. while (urls.hasMoreElements()) {
  16. URL url = urls.nextElement();
  17. UrlResource resource = new UrlResource(url);
  18. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  19. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  20. String factoryTypeName = ((String) entry.getKey()).trim();
  21. String[] factoryImplementationNames =
  22. StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
  23. for (String factoryImplementationName : factoryImplementationNames) {
  24. result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
  25. .add(factoryImplementationName.trim());
  26. }
  27. }
  28. }
  29. // Replace all lists with unmodifiable lists containing unique elements
  30. result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
  31. .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
  32. cache.put(classLoader, result);
  33. }
  34. catch (IOException ex) {
  35. throw new IllegalArgumentException("Unable to load factories from location [" +
  36. FACTORIES_RESOURCE_LOCATION + "]", ex);
  37. }
  38. return result;
  39. }

全局搜索META-INF下的:spring.factories

  • IDEA全局搜索快捷键:shift+shift
    在这里插入图片描述
  • spring.factories 自动配置根源所在
  • 也可以在外部库中查找
    在这里插入图片描述
  • 有很多很多自动配置的文件

遗憾·疑问spring.factories源码【看不到大量的自动装配源码】

  • 为什么spring boot2.7.3中找到的spring.factories与下图相差甚远?
    在这里插入图片描述
  • 甚至找不到WebMvcAutoConfiguration的自动装配,但却是存在这个WebMvcAutoConfiguration.java文件
  • 本来按照狂神说的,可以在里面找到WebMvcAutoConfiguration,然后找到WebMvcAutoConfiguration.java

在这里插入图片描述

  • 可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean。
  • 所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

结论

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  5. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

不平凡方法·SpringApplication

  1. @SpringBootApplication
  2. public class SpringbootApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(SpringbootApplication.class, args);
  5. }
  6. }
SpringApplication.run分析
  • 分析该方法主要分两部分

    • 一部分是SpringApplication的实例化
    • 二是run方法的执行;
  • SpringApplication

      1. 推断应用的类型是普通的项目还是Web项目
      1. 查找并加载所有可用初始化器 , 设置到initializers属性中
      1. 找出所有的应用程序监听器,设置到listeners属性中
      1. 推断并设置main方法的定义类,找到运行的主类
  • 查看构造器

    public SpringApplication(ResourceLoader resourceLoader, Class<?>… primarySources) {

    1. this.sources = new LinkedHashSet();
    2. this.bannerMode = Mode.CONSOLE;
    3. this.logStartupInfo = true;
    4. this.addCommandLineProperties = true;
    5. this.addConversionService = true;
    6. this.headless = true;
    7. this.registerShutdownHook = true;
    8. this.additionalProfiles = Collections.emptySet();
    9. this.isCustomEnvironment = false;
    10. this.lazyInitialization = false;
    11. this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    12. this.applicationStartup = ApplicationStartup.DEFAULT;
    13. this.resourceLoader = resourceLoader;
    14. Assert.notNull(primarySources, "PrimarySources must not be null");
    15. this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    16. this.webApplicationType = WebApplicationType.deduceFromClasspath();
    17. this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    18. this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    19. this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    20. this.mainApplicationClass = this.deduceMainApplicationClass();
    21. }
  • run方法
    \-

发表评论

表情:
评论列表 (有 0 条评论,199人围观)

还没有评论,来说两句吧...

相关阅读

    相关 springboot原理初探

    > 前言 本文是作者的入门笔记,学习狂射说,其中的主要知识,借鉴自狂神的课堂笔记。 本文章仅供学习和参考使用,没有任何商业的用途。如果对原作者造成损失,希望大

    相关 初探Spring - IOC原理

    一、IOC是什么 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来降低代码之间的耦合度。其中最常见的方式叫做依赖注