JavaWeb后端基础(8)spring原理
三部分:配置优先级 Bean的管理 剖析Springboot的底层原理
配置优先级
配置文件优先级排名(从高到低):
-
properties配置文件
-
yml配置文件
-
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
组件扫描
@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接口实现类
//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注解
-
第三方依赖中提供的注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类 public @interface EnableHeaderConfig { }
-
在使用时只需在启动类上加上@EnableXxxxx注解即可
@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 容器管理。