JavaWeb后端基础(8)spring原理

三部分:配置优先级 Bean的管理 剖析Springboot的底层原理

配置优先级

配置文件优先级排名(从高到低):

  1. properties配置文件

  2. yml配置文件

  3. yaml配置文件

虽然springboot支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置。(yml是主流)

在SpringBoot项目当中除了以上3种配置文件外,SpringBoot为了增强程序的扩展性,除了支持配置文件的配置方式以外,还支持另外两种常见的配置方式:

Java系统属性配置 (格式: -Dkey=value)

命令行参数 (格式:--key=value)

命令行参数的优先级时最高的,同时配置的情况下,命令行参数的配置项生效。

五种配置方式的优先级: 命令行参数 > 系统属性参数 > properties参数 > yml参数 > yaml参数

Bean的管理

在前面的课程当中,我们已经讲过了我们可以通过Spring当中提供的注解@Component以及它的三个衍生注解(@Controller、@Service、@Repository)来声明IOC容器中的bean对象,同时我们也学习了如何为应用程序注入运行时所需要依赖的bean对象,也就是依赖注入DI。

Bean的作用域

|-------------|---------------------------|
| 作用域 | 说明 |
| singleton | 容器内同名称的bean只有一个实例(单例)(默认) |
| prototype | 每次使用该bean时会创建新的实例(非单例) |
| request | 每个请求范围内会创建新的实例(web环境中,了解) |
| session | 每个会话范围内会创建新的实例(web环境中,了解) |
| application | 每个应用范围内会创建新的实例(web环境中,了解) |

可以借助Spring中的@Scope注解来进行配置作用域

  • IOC容器中的bean默认使用的作用域:singleton (单例)

  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)

  • prototype的bean,每一次使用该bean的时候都会创建一个新的实例

  • 实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性

第三方Bean

在我们项目开发当中,如果这个类它不是我们自己编写的,而是我们引入的第三方依赖当中提供的,那么此时我们是无法使用 @Component 及其衍生注解来声明bean的,此时就需要使用**@Bean**注解来声明bean 了。

  • 在启动类中直接声明这个Bean。比如:我们可以将我们之前使用的阿里云OSS操作的工具类,基于@Bean注解的方式来声明Bean。

  • 若要管理的第三方 bean 对象,建议对这些bean进行集中分类配置,可以通过 @Configuration 注解声明一个配置类。【推荐】

  • 通过@Bean注解的name 或 value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。

  • 如果第三方bean需要依赖其他bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。

SpringBoot原理

通过 SpringBoot来简化Spring框架的开发(是简化不是替代)。我们直接基于SpringBoot来构建Java项目,会让我们的项目开发更加简单,更加快捷。

SpringBoot框架之所以使用起来更简单更快捷,是因为SpringBoot框架底层提供了两个非常重要的功能:一个是起步依赖,一个是自动配置。

通过SpringBoot所提供的起步依赖,就可以大大的简化pom文件当中依赖的配置,从而解决了Spring框架当中依赖配置繁琐的问题。

通过自动配置的功能就可以大大的简化框架在使用时bean的声明以及bean的配置。我们只需要引入程序开发时所需要的起步依赖,项目开发时所用到常见的配置都已经有了,我们直接使用就可以了。

起步依赖

当我们引入了 spring-boot-starter-web 之后,maven会通过依赖传递特性,将web开发所需的常见依赖都传递下来。

自动配置

SpringBoot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

实现方案

1、@ComponentScan组件扫描

java 复制代码
@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要扫描的包
public class SpringbootWebConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfigApplication.class, args);
    }
}

如果采用这种方式来完成自动配置,那我们进行项目开发时,当需要引入大量的第三方的依赖,就需要在启动类上配置N多要扫描的包,这种方式会很繁琐。而且这种大面积的扫描性能也比较低。SpringBoot中并没有采用以上这种方案。

2、@Import导入

  • 导入形式主要有以下几种:

    • 导入普通类

    • 导入配置类

    • 导入ImportSelector接口实现类

      java 复制代码
      //1
      @Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中
      @SpringBootApplication
      public class SpringbootWebConfigApplication {
          public static void main(String[] args) {
              SpringApplication.run(SpringbootWebConfigApplication.class, args);
          }
      }
      
      
      //2.1配置类
      @Configuration
      public class HeaderConfig {
          @Bean
          public HeaderParser headerParser(){
              return new HeaderParser();
          }
      
          @Bean
          public HeaderGenerator headerGenerator(){
              return new HeaderGenerator();
          }
      }
      
      //2.2启动类
      @Import(HeaderConfig.class) //导入配置类
      @SpringBootApplication
      public class SpringbootWebConfig2Application {
          public static void main(String[] args) {
              SpringApplication.run(SpringbootWebConfig2Application.class, args);
          }
      }
      
      //3.1接口实现类
      public class MyImportSelector implements ImportSelector {
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              //返回值字符串数组(数组中封装了全限定名称的类)
              return new String[]{"com.example.HeaderConfig"};
          }
      }
      //启动类
      @Import(MyImportSelector.class) //导入ImportSelector接口实现类
      @SpringBootApplication
      public class SpringbootWebConfig2Application {
          public static void main(String[] args) {
              SpringApplication.run(SpringbootWebConfig2Application.class, args);
          }
      }

4、使用第三方依赖提供的 @EnableXxxxx注解

  • 第三方依赖中提供的注解

    java 复制代码
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
    public @interface EnableHeaderConfig { 
    }
  • 在使用时只需在启动类上加上@EnableXxxxx注解即可

    java 复制代码
    @EnableHeaderConfig  //使用第三方依赖提供的Enable开头的注解
    @SpringBootApplication
    public class SpringbootWebConfig2Application {
        public static void main(String[] args) {
            SpringApplication.run(SpringbootWebConfig2Application.class, args);
        }
    }

自动配置原理

自动配置原理源码入口就是 @SpringBootApplication 注解,在这个注解中封装了3个注解,分别是

  • @SpringBootConfiguration

    • 声明当前类是一个配置类
  • @ComponentScan

    • 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)
  • @EnableAutoConfiguration

    • 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)

在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。

当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

Conditional

我们在跟踪SpringBoot自动配置的源码的时候,在自动配置类声明bean的时候,除了在方法上加了一个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。以Conditional开头的这些注解都是条件装配的注解。

@Conditional注解:

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。

  • 位置:方法、类

  • @Conditional本身是一个父注解,派生出大量的子注解:

    • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

    • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

    • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器

重点总结

自动配置的核心就在@SpringBootApplication注解上,SpringBootApplication这个注解底层包含了3个注解,分别是:

  • @SpringBootConfiguration

  • @ComponentScan

  • @EnableAutoConfiguration

@EnableAutoConfiguration这个注解才是自动配置的核心。

  • 它封装了一个@Import注解,Import注解里面指定了一个ImportSelector接口的实现类。

  • 在这个实现类中,重写了ImportSelector接口中的selectImports()方法。

  • 而selectImports()方法中会去读取两份配置文件,并将配置文件中定义的配置类做为selectImports()方法的返回值返回,返回值代表的就是需要将哪些类交给Spring的IOC容器进行管理。

  • 那么所有自动配置类的中声明的bean都会加载到Spring的IOC容器中吗? 其实并不会,因为这些配置类中在声明bean时,通常都会添加@Conditional开头的注解,这个注解就是进行条件装配。而Spring会根据Conditional注解有选择性的进行bean的创建。

  • @Enable 开头的注解底层,它就封装了一个注解 import 注解,它里面指定了一个类,是 ImportSelector 接口的实现类。在实现类当中,我们需要去实现 ImportSelector 接口当中的一个方法 selectImports 这个方法。这个方法的返回值代表的就是我需要将哪些类交给 spring 的 IOC容器进行管理。

  • 此时它会去读取两份配置文件,一份儿是 spring.factories,另外一份儿是 autoConfiguration.imports。而在 autoConfiguration.imports 这份儿文件当中,它就会去配置大量的自动配置的类。

  • 而前面我们也提到过这些所有的自动配置类当中,所有的 bean都会加载到 spring 的 IOC 容器当中吗?其实并不会,因为这些配置类当中,在声明 bean 的时候,通常会加上这么一类@Conditional 开头的注解。这个注解就是进行条件装配。所以SpringBoot非常的智能,它会根据 @Conditional 注解来进行条件装配。只有条件成立,它才会声明这个bean,才会将这个 bean 交给 IOC 容器管理。

相关推荐
stevenzqzq4 小时前
trace和Get thread dump的区别
java·android studio·断点
桦说编程4 小时前
并发编程踩坑实录:这些原则,帮你少走80%的弯路
java·后端·性能优化
程序猿零零漆4 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(十三)SpringMVC快速入门、请求处理
java·学习·spring
BHXDML4 小时前
JVM 深度理解 —— 程序的底层运行逻辑
java·开发语言·jvm
用户8307196840824 小时前
Shiro登录验证与鉴权核心流程详解
spring boot·后端
tkevinjd4 小时前
net1(Java中的网络编程、TCP的三次握手与四次挥手)
java
码头整点薯条4 小时前
基于Java实现的简易规则引擎(日常开发难点记录)
java·后端
J2虾虾4 小时前
Java使用的可以使用的脚本执行引擎
java·开发语言·脚本执行
曲幽4 小时前
FastAPI登录验证:用OAuth2与JWT构筑你的API安全防线
python·fastapi·web·jwt·token·oauth2
老马识途2.04 小时前
java处理接口返回的json数据步骤 包括重试处理,异常抛出,日志打印,注意事项
java·开发语言