1. Bean的作用域
在SpringloC&DI阶段,我们学习了Spring是如何帮助我们管理对象的.
- 通过@Controller,@Service,@Repository,@Component,@Configuration,@Bean来声明Bean对象.
- 通过ApplicationContext或者BeanFactory来获取对象
- 通过@Autowired,Setter方法或者构造方法等来为应用程序注入所依赖的Bean对象
我们来简单回顾一下
1.通过@Bean声明 bean,把bean存在Spring容器中


- 从Spring 容器中获取Bean

也可以通过在代码中直接注入ApplicationContext的方式来获取Spring容器

修改代码,从Spring中多次获取Bean

观察运行结果:

发现输出的bean对象地址值是一样的,说明每次从Spring容器中取出来的对象都是同一个。
- 这也是"单例模式"
- 单例模式:确保一个类只有一个实例,多次创建也不会创建出多个实例
默认情况下,Spring容器中的bean都是单例的,这种行为模式,我们就称之为Bean的作用域 。
Bean的作用域是指Bean在Spring框架中的某种行为模式。
比如单例作用域:表示Bean在整个Spring中只有一份,它是全局共享的,那么当其他人修改了这个值之后,另一个人读取到的就是被修改的值。
修改上述代码,给Dog添加属性name
修改测试代码:

观察运行结果:

那么能不能将bean对象设置为非单例的(每次获取的bean都是一个新对象呢)?
这就是Bean的不同作用域了。
① Bean的作用域
在Spring中支持6种作用域,后4种在SpringMVC环境才生效
- singleton:单例作用域
- prototype:原型作用域(多例作用域)
- request:请求作用域
- session:会话作用域
- Application: 全局作用域
- websocket: HTTP WebSocket 作用域
这里介绍一下 @Scope注解,@Scope 注解的核心作用是定义 Bean 实例的生命周期和可见范围,即控制对象的创建、复用和销毁规则。
在 Spring 框架中:定义 Bean 作用域,且@Scope 用于指定 @Component 或 @Bean 所定义对象的作用域。
主要作用域类型:
| 作用域 | 说明 |
|---|---|
| singleton | 每个 Spring IoC 容器内同名称的 bean 只有一个实例 (单例)(默认) |
| prototype | 每次使用该 bean 时( getBean() ) 都会创建新的实例 (多例) |
| request | 每个 HTTP 请求生命周期内,创建新的实例 (web 环境中) |
| session | 每个 HTTP Session 生命周期内,创建新的实例 (web 环境中) |
| application | 每个 ServletContext 生命周期内,创建新的实例 (web 环境中) |
| websocket | 每个 WebSocket 生命周期内,创建新的实例 (web 环境中) |
通过代码开观察Bean的作用域
1. 单例作用域


访问:http://127.0.0.1:8080/dog/singleton

多次访问,得到的都是同一个对象,并且 @Resource 和 context.getBean() 也是同一个对象。
2. 多例作用域


访问:http://127.0.0.1:8080/dog/prototype


观察ContextDog, 每次获取的对象都不⼀样,而注入的对象在Spring容器启动时, 就已经注了, 所以多次请求也不会发生变化。
3. 请求作用域
@RequestScope 注解等于 @Scope("request") 注解:



访问:http://127.0.0.1:8080/dog/request


在一次请求中,@Resource 和 context.getBean()也是同一个对象,但是每次请求,都会重新创建对象。
4. 会话作用域


访问:http://127.0.0.1:8080/dog/session
在⼀个session中, 多次请求, 获取到的对象都是同⼀个

换⼀个浏览器访问, 发现会重新创建对象.(另⼀个Session)

5. Application 作用域


访问:http://127.0.0.1:8080/dog/application
在⼀个应用中, 多次访问都是同⼀个对象

换一个浏览器也是一样

Application scope就是对于整个web容器来说,bean的作用域是ServletContext级别的。
这个和singleton有点类似,区别在于: Applicationscope是ServletContext的单例,singleton是一个ApplicationContext 的单例 。在一个web容器中ApplicationContext可以有多个。
2. Bean的生命周期
生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期。
Bean的生命周期分为以下5个部分:
- 1.实例化(为Bean分配内存空间)
- 2.属性赋值(Bean注入和装配,比如@Autowired)
- 3.初始化
- a.执行各种通知,如BeanNameAware,BeanFactoryAware,ApplicationContextAware的接口方法.
- b.执行初始化方法
- xml定义init-method
- 使用注解的方式@PostConstruct
- 执行初始化后置方法(BeanPostProcessor)
-
- 使用Bean
- 5.销毁Bean
- a.销毁容器的各种方法,如@PreDestroy,DisposableBean接口方法,destroy-method.
实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段,可以在实例化之后,类加载完成之前进行自定义"事件"处理。
比如我们现在需要买一栋房子,那么我们的流程是这样的:
1.先买房(实例化,从无到有)
2.装修(设置属性)
3.买家电,如洗衣机,冰箱,电视,空调等(各种初始化,可以入住);
4.入住(使用Bean)
5.卖房(Bean销毁)
通过代码演示:

测试:

执行结果:

通过运行结果观察 1. 先执行构造函数 2. 设置属性 3. Bean初始化 4. 使用Bean 5. 销毁Bean
3. Spring Boot 自动配置
SpringBoot的自动配置就是当Spring容器启动后,一些配置类,bean对象等就自动存入到了loC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
SpringBoot自动配置,就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到SpringloC容器中的。
我们学习主要分以下两个方面:
1.Spring是如何把对象加载到SpringloC容器中的
2.SpringBoot 是如何实现的
① Spring 加载Bean
**需求:**使用Spring管理第三方的jar包的配置
- 引入第三方的包,其实就是在该项目下,引入第三方的代码,我们采用在该项目下创建不同的目录来模拟第三方的代码引入
数据准备:
1.创建项目spring-autoconfig,当前项目目录为com.example.principle
2.模拟第三方代码文件在com.example.autoconfig目录下

第三方文件代码:

- 获取 UserConfig 这个Bean

运行程序:

观察日志: No qualifying bean of type 'com.bite.autoconfig.BiteConfig' available
没有com.example.autoconfig.UserConfig这个类型的Bean
② 原因分析
Spring通过五大注解和@Bean注解可以帮助我们把Bean加载到SpringloC容器中,以上有个前提就是这些注解类需要和SpringBoot启动类在同一个目录下 (@SpringBootApplication标注的类就
是SpringBoot项目的启动类).
启动类所在目录为:com.example.principle,而UserConfig这个类在com.example.autoconfig下,所以SpringBoot并没有扫描到。
当我们引入第三方的Jar包时,第三方的Jar代码目录肯定不在启动类的目录下,如何告诉Spring帮我们管理这些Bean呢?
③ 解决方案
我们需要指定路径或者引入的文件, 告诉Spring, 让Spring进行扫描到。
1. @ComponentScan 组件扫描
通过 @ComponentScan 注解, 指定Spring扫描路径

也可以指定扫描多个包: 例如:@ComponentScan({"com.example.autoconfig","com.example.principle"})
运行结果:

可以看到,这次UserConfig Bean获取到了
Spring是否使用了这种方式呢?
非常明显,没有,(因为我们引入第三方框架时,没有加扫描路径。比如mybatis)
如果SpringBoot采用这种方式,当我们引|入大量的第三方依赖,比如Mybatis,jackson等时,就要在启动类上配置不同依赖需要扫描的包,这种方式会非常繁琐。
2. @Import 导入
使用@Import导入的类会被Spring加载到IoC容器中。
@Import 导入主要有以下几种形式:
- 导入类
- 导入ImportSelector接口实现类
导入类

运行程序:

问题:如果又有多了一些配置项呢?

可以采用导入多个类:

很明显, 这种方式也很繁琐。 所以, SpringBoot依然没有采用。
2. 导入ImportSelector接口实现类
ImportSelector 接口实现类:

启动类:

运行程序:

可以看到, 我们采用这种方式也可以导入第三方依赖提供的Bean。
问题:
但是他们都有一个明显的问题,就是使用者需要知道第三方依赖中有哪些Bean对象或配置类,如果漏掉其中一些Bean,很可能导致我们的项目出现大的事故。
这对程序员来说非常不友好。
依赖中有哪些Bean,使用时需要配置哪些bean,第三方依赖最清楚,那能否由第三方依赖来做这件事呢?
- 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以**@EnableXxxx开头** 的注解,注解中封装的就是@Import注解
1.第三方依赖提供注解

注解中封装 @Import 注解, 导入 MySelector.class
- 在启动类上使用第三方提供的注解

运行程序:

可以看到,这种方式也可以导入第三方依赖提供的Bean。
并且这种方式更优雅一点.SpringBoot采用的也是这种方式。
4. Spring原理分析
① 源码阅读
SpringBoot 是如何帮助我们做的呢? ⼀切的来自起源SpringBoot的启动类开始 @SpringBootApplication 标注的类 就是SpringBoot项目的启动类

@SpringBootApplication 注解是SpringBoot实现自动配置的核心。
@SpringBootApplication是一个组合注解,注解中包含了:
1.元注解
JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为meta-annotation(元注
解),他们分别是:
- @Target描述注解的使用范围(即被修饰的注解可以用在什么地方)
- @Retention描述注解保留的时间范围
- @Documented描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息
- @Inherited使被它修饰的注解具有继承性(如果某个类使用了被@lnherited修饰的注解,则其子类将自动具有该注解)
2. @SpringBootConfiguration
里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已。 (@Indexed注解,是用来加速应用启动的,不用关心)

3.@EnableAutoConfiguration(开启自动配置)
Spring自动配置的核心注解,下面详细讲解。
4.@ComponentScan(包扫描)
可以通过 basePackageClasses 或 basePackages 来定义要扫描的特定包,如果没有定义特定的包,将从声明该注解的类的包开始扫描,这也是为什么SpringBoot项自声明的注解类必须要在启动类的目录下。
(1) @EnableAutoConfiguration 详解
看下 @EnableAutoConfiguration 注解的实现:

这个注解包含两部分:
1. @Import({[AutoConfigurationlmportSelector.class})
使用@lmport注解,导入了实现ImportSelector接口的实现类.

selectImports() 方法底层调用 getAutoConfigurationEntry() 方法, 获取可自动配置的配置类信息集合。
点进去:

getAutoConfigurationEntry()方法通过调用getCandidateConfigurations(annotationMetadata,attributes)方法获取在配置文件中配置的所有自动配置类的集合。
点进去:
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
getCandidateConfigurations方法的功能:获取所有基于
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,META-INF/spring.factories文件中配置类的集合.
在引入的起步依赖中,通常都有包含以上两个文件

这里面包含了很多第三方依赖的配置文件(连续按两下shift可以查看对应的源码)
- 在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。
- @Conditional是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
- META-INF/spring.factories文件是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件中配置即可,Spring就会把相应的实现类注入到Spring容器中。
- 注:会加载所有jar包下的classpath路径下的META-INF/spring.factories文件,这样文件不止一个.
示例,我们加入mybatis-plus 和 MySQL驱动的依赖:
XML
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
然后就会引入第三方配置文件:

2. @AutoConfigurationPackage
源码如下:

这个注解主要是导⼊⼀个配置文件 AutoConfigurationPackages.Registrar.class

Registrar实现了ImportBeanDefinitionRegistrar类,就可以被注解@lmport导入到spring容器里。
(String\[\]) (new PackageImports(metadata)) .getPackageNames() .toArray(newStringo):当前启动类所在的包名.
所以:@AutoConfigurationPackage就是将启动类所在的包下面所有的组件都扫描注册到spring 容器中.