【JavaEE】(25) Spring 原理
一、Bean 的作用域
Bean 的作用域指 Bean 的某种行为模式。
1、单例作用域(singleton,默认)
在整个 Spring IoC 容器内,同名 bean 只有一个。无论用什么方式获取同名 Bean,都是一样的,并且某一地方修改了 Bean,其它地方使用的也是修改后的值。
存储 Bean 时,若未指定作用域,默认为单例作用域。
// 类注解,存储 Bean
@Configuration
public class BeanConfig {
}
@RestController
@RequestMapping("/scope")
public class ScopeController {@Autowiredprivate ApplicationContext context;// 默认情况,单例作用域@Autowiredprivate 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 作用域@Autowiredprivate 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 作用域@Autowiredprivate Dog prototype;// 测试原型作用域@RequestMapping("testPrototype")public String testPrototype() {return "context bean: " + context.getBean("prototype") + "<br/>" +"config bean: " + prototype;}
下面不变的原因:@AutiWired
3、请求作用域(request)
每次 HTTP 请求内的同名 bean 相同。
// 指定作用域为 request@Bean@RequestScopepublic Dog request() {return new Dog();}
// request 作用域@Autowiredprivate Dog request;// 测试请求作用域@RequestMapping("testRequest")public String testRequest() {return "context bean: " + context.getBean("request") + "<br/>" +"config bean: " + request;}
每次请求,会新建 bean。
4、会话作用域(session)
每个 HTTP 会话中,同名 bean 相同。
// 指定作用域为 session@Bean@SessionScopepublic Dog session() {return new Dog();}
// session 作用域@Autowiredprivate 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@ApplicationScopepublic Dog application() {return new Dog();}
// application 作用域@Autowiredprivate 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. 属性赋值@Autowiredpublic void setBeanConfig(BeanConfig beanConfig) {this.beanConfig = beanConfig;System.out.println("执行 setter 方法...");}// 3. 初始化 a. 执行各种通知@Overridepublic void setBeanName(String s) {System.out.println("执行 BeanNameAware 接口的 setBeanName 方法,beanName = " + s);}// 4. 初始化 b. 执行自定义初始化方法(注解方式)@PostConstructpublic void init() {System.out.println("执行自定义初始化方法...");}// 5. 使用 beanpublic void use() {System.out.println("执行 user 方法...");}// 6. 自定义销毁方法(注解方式)@PreDestroypublic void destroy() {System.out.println("执行自定义销毁方法...");}
}
在测试中使用,能演示销毁:
@Testvoid 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@Testvoid 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 {@Overridepublic 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 自动配置原理流程: