一、Bean 的作用域
Bean 的作用域指 Bean 的某种行为模式。
1、单例作用域(singleton,默认)
在整个 Spring IoC 容器内,同名 bean 只有一个。无论用什么方式获取同名 Bean,都是一样的,并且某一地方修改了 Bean,其它地方使用的也是修改后的值。
存储 Bean 时,若未指定作用域,默认为单例作用域。
// 类注解,存储 Bean
@Configuration
public class BeanConfig {
}
@RestController
@RequestMapping("/scope")
public class ScopeController {
@Autowired
private ApplicationContext context;
// 默认情况,单例作用域
@Autowired
private BeanConfig beanConfig;
// 测试默认作用域
@RequestMapping("test")
public String test() {
return "context bean: " + context.getBean(BeanConfig.class) + "<br/>" +
"config bean: " + beanConfig;
}
}
也可以指定单例作用域:
public class Dog {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 未指定作用域,默认单例作用域
@Configuration
public class BeanConfig {
// 指定作用域为 singleton
@Bean
// @Scope("singleton")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleton() {
return new Dog();
}
}
// singleton 作用域
@Autowired
private Dog singleton;
// 测试单例作用域
@RequestMapping("testSingleton")
public String testSingleton() {
return "context bean: " + context.getBean("singleton") + "<br/>" +
"config bean: " + singleton;
}
重启后,Spring IoC 容器销毁,bean 也销毁了。
2、原型作用域(prototype,多例)
每次使用同名 bean,都会新建实例。
// 指定作用域为 prototype
@Bean
// @Scope("prototype")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototype() {
return new Dog();
}
// prototype 作用域
@Autowired
private Dog prototype;
// 测试原型作用域
@RequestMapping("testPrototype")
public String testPrototype() {
return "context bean: " + context.getBean("prototype") + "<br/>" +
"config bean: " + prototype;
}
下面不变的原因:@AutiWired
3、请求作用域(request)
每次 HTTP 请求内的同名 bean 相同。
// 指定作用域为 request
@Bean
@RequestScope
public Dog request() {
return new Dog();
}
// request 作用域
@Autowired
private Dog request;
// 测试请求作用域
@RequestMapping("testRequest")
public String testRequest() {
return "context bean: " + context.getBean("request") + "<br/>" +
"config bean: " + request;
}
每次请求,会新建 bean。
4、会话作用域(session)
每个 HTTP 会话中,同名 bean 相同。
// 指定作用域为 session
@Bean
@SessionScope
public Dog session() {
return new Dog();
}
// session 作用域
@Autowired
private Dog session;
// 测试 session 作用域
@RequestMapping("testSession")
public String testSession() {
return "context bean: " + context.getBean("session") + "<br/>" +
"config bean: " + session;
}
不同浏览器,会话不同,新建 bean。
5、全局作用域(application)
ServletContext 内同名 bean 相同。ServletContext 可以认为是一个 Tomcat 容器,一个 Tomcat 可包含多个 ApplicationContext。实际开发中几乎没用,因为 Spring Boot 项目中,一个 Tomcat 只能包含一个 ApplicationContext;并且 ApplicationContext 在业务上也是隔离的,不可能这个 ApplicationContext 的 bean 放到另一个 ApplicationContext 里面用。
// 指定作用域为 application
@Bean
@ApplicationScope
public Dog application() {
return new Dog();
}
// application 作用域
@Autowired
private Dog application;
// 测试全局作用域
@RequestMapping("testApplication")
public String testApplication() {
return "context bean: " + context.getBean("application") + "<br/>" +
"config bean: " + application;
}
6、WebSocket 作用域
每个 WebSocket 内的同名 bean 相同。
二、Bean 的生命周期
1、概念
- 实例化:给 bean 分配内存空间。
- 属性赋值:注入属性里的 bean,如 @AutoWired。
- 初始化:a. 执行各种通知,如 BeanNameAware 接口。b. 执行初始化:初始化的自定义扩展内容实现:(注解方式) @PostConstruct;(xml 方式) init-method;(实现接口方式) BeanPostProcessor。
- 使用。
- 销毁:销毁的自定义扩展内容实现:(注解方式) @PreDestroy;(xml 方式) destroy-method;(实现接口方式) DisposableBean。
执行顺序图示:
2、代码演示
// 将 BeanLifeComponent 注册为 Spring Bean
@Component
public class BeanLifeComponent implements BeanNameAware {
private BeanConfig beanConfig;
// 1. 实例化
public BeanLifeComponent() {
System.out.println("执行构造函数...");
}
// 2. 属性赋值
@Autowired
public void setBeanConfig(BeanConfig beanConfig) {
this.beanConfig = beanConfig;
System.out.println("执行 setter 方法...");
}
// 3. 初始化 a. 执行各种通知
@Override
public void setBeanName(String s) {
System.out.println("执行 BeanNameAware 接口的 setBeanName 方法,beanName = " + s);
}
// 4. 初始化 b. 执行自定义初始化方法(注解方式)
@PostConstruct
public void init() {
System.out.println("执行自定义初始化方法...");
}
// 5. 使用 bean
public void use() {
System.out.println("执行 user 方法...");
}
// 6. 自定义销毁方法(注解方式)
@PreDestroy
public void destroy() {
System.out.println("执行自定义销毁方法...");
}
}
在测试中使用,能演示销毁:
@Test
void contextLoads() {
BeanLifeComponent bean = (BeanLifeComponent) context.getBean("beanLifeComponent");
bean.use();
}
3、源码阅读
查找 AbstractAutowireCapableBeanFactory 类的 doCreateBean 方法。
(1)实例化
doCreateBean:
createBeanInstance:
(2)属性赋值
doCreateBean:
populateBean(填充 bean):
(3)初始化
doCreateBean:
initializeBean:
逐个分析:
① invokeAwareMethouds:
② applyBeanPostProcessorsAfterInitialization:
③ invokeInitMethods:
(4)销毁
doCreateBean:把 bean 的销毁方法注册到 Spring。
三、Spring Boot 自动配置
Spring 自动装配指的是 DI(依赖注入)。而 Spring 自动配置指的是,Spring 项目启动后,第三方 jar 包里的一些类、bean 对象就自动存入了 Srping 容器,无需使用 jar 包者手动声明存入。
1、Spring 管理第三方配置类(问题重现)
配置类:指的是第三方包希望让 Spring 管理的类,而不是所有类。
模拟第三方 jar 包:
Config1:2、3 类似
package com.edu.spring.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config1 {
}
由于启动类在我们的项目中,所以扫描不到第三方的类,Spring 也就无法管理它的 bean:
// 测试获取第三方 bean
@Test
void testConfig() {
Config1 config1 = context.getBean(Config1.class);
System.out.println(config1);
}
(1)方法1(@Component)
手动声明需要扫描的类所在路径:
但这种方式不好。一是麻烦,需要一个个 jar 包声明路径。二是有时不希望让 jar 包中的所有类的 bean 让 Spring 管理(jar 包使用者可能不会用到所有功能,所以有些类所需的依赖,pom 文件中根本没有引入,让 Spring 管理反而有问题。比如我们模拟的第三方,希望 Config1、Config2 被管理,不希望 Config3),@Component 就无法做到这一点。
(2)方法2(@Import)
将希望被 Spring 管理的类一个个列到 @Import 中:(可以做到让 jar 包部分类被管理)
但这种方式仍然很麻烦。并且使用者不清楚哪些类希望被管理,只有 jar 包开发者最清楚。
(3)方法3(第三方提供注解,Spring Boot 采用)
这种注解常以 @Enable 开头。第三方提供的注解,里面列出了需要被管理的类,使用者只需加上该注解即可,类似于一些中间件,会提供 @Enablexxx 注解开启第三方服务。
实现 ImportSelector 接口,重写方法指定需要被管理的类:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.edu.spring.config.Config1",
"com.edu.spring.config.Config2"};
}
}
@Enablexxx 注解定义:
使用者加入注解,即可让第三方的 bean 让 Spring 管理:
但这还不够好,因为每加入一个第三方中间件,都要手动加注解,不够方便。
(4)方法4(约定配置类文件路径,Spring Boot 采用)
Spring Boot 3.0.2 约定的自动配置类集合路径:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,创建该路径:
列出需要被自动配置的类:
第三方包举例:pom 文件引入 mybatis 依赖,Spring Boot 会自动扫描 MyBatis 约定自动配置类文件的内容:
2、源码阅读
启动类上的 @SpringBootApplication 注解:
深入 @EnableAutoConfiguration:
(1)@Import({AutoConfigurationImportSelector.class})
导入了 AutoConfigurationImportSelector.class,它实现了 ImportSelector 接口的 selectImports 方法:
深入阅读 getAutoConfigurationEntry():debug,观察结果。
深入阅读 getCandidateConfigurations(annotationMetadata, attributes):文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
查看自动配置类文件:我的是 Spring Boot 3.0.2 版本,这里面存放的是原生的+集成的第三方的。
这些配置类需要排除掉用户未使用的,比如 Rabbit,我们在 POM 中没有引入该依赖,所有 Rabbit 源码中导入不了相关类,导致很多报红错误:
查看一个我们之前了解的 JDBC 事务源码:
源码使用的 @Conditional 有条件地将 bean 交给 Spring 管理:(未达到条件的自动配置类、bean 就会被排除掉)因为我们的 pom 文件没有引入 JDBC 事务的依赖,所以找不到指定类,也就不会自动配置 DataSourceTransaction.... 类。 这也就是排除的原理。
一些常见的 @Conditional:
(2)@AutoConfigurationPackage
深入阅读 @AutoConfigurationPackage:
AutoConfigurationPackages.Registrar.class:
如 MyBatis 实现了 ImportBeanDefinitionRegistrar 接口的 registerBeanDefinitions 方法,就可以扫描我们的项目中被 @Mapper 声明的 bean:
3、总结
Spring Boot 自动配置原理流程: