目次
Bean 的作用域
单例作用域
多例作用域
请求作用域
会话作用域
全局作用域
Bean 的生命周期
Bean 的作用域
在 Spring IoC和DI-CSDN博客 中,我们学习了 Spring 是如何资助我们管理对象的:
(1)通过 @Controller、@Service、@Repository、@Component、@Configuration、@Bean 来声明 Bean
(2)通过 ApplicaationContext 或 BeanFactory 来获取Bean
(3)通过 属性注入、Setter 方法注入 或 构造方法注入等为程序注入所依赖的 Bean 对象
比方:
- public class Student {
- private int id;
- private String name;
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
复制代码 使用 @Bean 声明 bean,将 bean 存储在 Spring 容器中:
- @Component
- public class StudentBeanConfig {
- @Bean
- public Student student() {
- return new Student();
- }
- }
复制代码 从 Spring 容器中获取 Bean:
- @SpringBootApplication
- public class SpringBeanApplication {
- public static void main(String[] args) {
- ApplicationContext context = SpringApplication.run(SpringBeanApplication.class, args);
- Student student = context.getBean(Student.class);
- System.out.println(student);
- }
- }
复制代码 也可以直接注入 ApplicationContext,获取 Spring 容器,从而获取 Bean:
- @SpringBootTest
- class SpringBeanApplicationTests {
- @Autowired
- private ApplicationContext context;
- @Test
- void contextLoads() {
- Student student = context.getBean(Student.class);
- System.out.println(student);
- }
- }
复制代码 从 Spring 容器中多次获取 Bean:
- @SpringBootTest
- class SpringBeanApplicationTests {
- @Autowired
- private ApplicationContext context;
- @Test
- void contextLoads() {
- Student student = context.getBean(Student.class);
- System.out.println(student);
- Student student1 = context.getBean(Student.class);
- System.out.println(student1);
- }
- }
复制代码 运行并观察效果:
可以看到,两次输出的 bean 地址值是相同的,也就是说,每次从 Spring 容器中取出的对象是同一个
这样的模式也称为单例模式(一个类只有一个实例,多次创建也不会创建出多个实例)
默认情况下,Spring 容器中的 bean 都是单例的,这种行为模式,我们就称为 Bean 的作用域
Bean 的作用域是指 Bean 在Spring 框架中的某种行为模式
就如上述单例作用域,表现的是 Bean 在整个 Spring 中只有一份,是全局共享的。在一个方法中修改了它的值,其他方法读取到的就是被修改后的值:
- @SpringBootTest
- class SpringBeanApplicationTests {
- @Autowired
- private ApplicationContext context;
- @Test
- void contextLoads() {
- Student student = context.getBean(Student.class);
- student.setId(1);
- System.out.println(student.getId());
- student.setId(2);
- System.out.println(student.getId());
- Student student1 = context.getBean(Student.class);
- System.out.println(student.getId());
- }
- }
复制代码 运行效果:
student 和 student1 是同一个对象,student1 拿到了 student 设置的值
那能不能让 student 和 student1 为差异对象,也就是每次获取的 bean 都是一个新的对象呢?
这就是 Bean 的差异作用域了
在 Spring 中支持 6 种作用域,其中,后 4 种在 Spring MVC 环境下才生效
作用域说明singleton(单例作用域)每个 Spring Ioc 容器内同名称的 bean 只有一个实例(默认)prototype(原型/多例作用域)每次使用 bean 时都会创建新的实例request(请求作用域)每个 HTTP 请求生命周期内,创建新的实例(web 环境中)session(会话作用域)每个 HTTP Session 生命周期内,创建新的实例(web 环境中)application(全局作用域)每个 ServletContext 生命周期内,创建新的实例(web 环境中)websocket(HTTP WebSocket 作用域)每个 WebSocket 生命周期内,创建新的实例(web 环境中) 接下来,我们分别来看差异作用域下的 Bean (由于 websocket 作用域很少使用,在这里就不进实现观察了)
界说差异作用域的 Bean:
使用 @Scope 界说 Bean 的作用域:
- @Component
- public class StudentBeanConfig {
- @Bean
- @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
- public Student singleStudent() {
- return new Student();
- }
- @Bean
- @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
- public Student prototypeStudent() {
- return new Student();
- }
- @Bean
- @RequestScope
- public Student requestStudent() {
- return new Student();
- }
- @Bean
- @SessionScope
- public Student sessionStudent() {
- return new Student();
- }
-
- @Bean
- @ApplicationScope
- public Student applicationStudent() {
- return new Student();
- }
- }
复制代码 在 ConfigurableBeanFactory 中只界说了 单例作用域 和 多例作用域:
而在界说其他作用域的 Bean 时,可以使用对应的注解
比方,请求作用域,可以使用 @RequestScope
我们来看 @RequestScope 注解:
因此,也可以使用 @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
其中,proxyMode 用来为 spring bean 设置署理,proxyMode = ScopedProxyMode.TARGET_CLASS 表现这个 Bean 基于 CGLIB 实现动态署理,Request、session 和 application 作用域的 Bean 需要设置 proxyMode
同理,
@SessionScope 等同于 @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
@ApplicationScope 等同于 @Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
接着,我们就来测试差异作用域的 bean 获取到的对象是否相同:
- @RestController
- @RequestMapping("/student")
- public class StudentController {
- @Resource(name = "singleStudent")
- private Student singleStudent;
- @Resource(name = "prototypeStudent")
- private Student prototypeStudent;
- @Resource(name = "requestStudent")
- private Student requestStudent;
- @Resource(name = "sessionStudent")
- private Student sessionStudent;
- @Resource(name = "applicationStudent")
- private Student applicationStudent;
- @Autowired
- private ApplicationContext context;
- @RequestMapping("/single")
- public String single() {
- Student student1 = (Student) context.getBean("singleStudent");
- return "student: " + singleStudent + "</br> contextStudent: " + student1;
- }
- @RequestMapping("/prototype")
- public String prototype() {
- Student student1 = (Student) context.getBean("prototypeStudent");
- return "student: " + prototypeStudent + "</br> contextStudent: " + student1;
- }
- @RequestMapping("/request")
- public String request() {
- Student student1 = (Student) context.getBean("requestStudent");
- return "student: " + requestStudent + "</br> contextStudent: " + student1;
- }
- @RequestMapping("/session")
- public String session() {
- Student student1 = (Student) context.getBean("sessionStudent");
- return "student: " + sessionStudent + "</br> contextStudent: " + student1;
- }
- @RequestMapping("/application")
- public String application() {
- Student student1 = (Student) context.getBean("applicationStudent");
- return "student: " + applicationStudent + "</br> contextStudent: " + student1;
- }
- }
复制代码 我们让每个请求都获取两次 Bean
无论是 @Autowired 还是 context.getBean 都是从 Spring 容器中获取对象
接下来,我们运行程序,观察 Bean 的作用域
单例作用域
访问 http://127.0.0.1:8080/student/single,观察单例作用域:
@Autowired 和 context.getBean 得到的是同一个对象,且多次访问得到的也是同一个对象
多例作用域
访问 http://127.0.0.1:8080/student/prototype,观察多例作用域:
@Autowired 和 context.getBean 得到的是差异对象,且多次访问,使用 context.getBean 得到的是差异对象,而使用 @Autowired 得到的是同一个对象
这是因为使用 @Autowired 注入的对象在 Spring 容器启动时,就已经注入了,全部多次请求也不会发生变化
而 context.getBean 则是在访问接口时才获取对象,因此多次请求获取到的对象差异
请求作用域
访问 http://127.0.0.1:8080/student/request,观察请求作用域:
一次请求中, @Autowired 和 context.getBean 得到的是相同对象,但每次请求,都会重新创建对象,因此差异请求得到的对象是差异的
会话作用域
访问 http://127.0.0.1:8080/student/session,观察会话作用域:
多次请求,得到的是相同对象
我们换一个浏览器访问,就会发现此时就创建了新的对象:
全局作用域
访问 http://127.0.0.1:8080/student/application,观察 Application 作用域:
多次访问都是同一个对象
Application scope 就是对于整个 web 容器来说,bean 的作用域是 ServletContext 级别的
这与 singleton 类似,但与 singletion 的区别在于:Application scope 是 ServletContext 的单例,而 singleton 是一个 ApplicationContext 的单例,在一个 web 容器中 ApplicationContext 可以有多个
在相识了 Bean 的作用域后,我们继承学习 Bean 的生命周期
Bean 的生命周期
生命周期指的是一个对象从诞生到烧毁的整个生命过程,这个过程就叫做一个对象的生命周期
Bean 的生命周期可以分为以下 5 个部分:
1. 实例化(为 Bean 分配内存空间,实例化一个 Bean 对象)
2. 属性赋值(Bean 的注入和装配,设置相关属性和依赖)
3. 初始化(执行各种关照 和 初始化方法)
4. 使用 Bean
5. 烧毁 Bean(执行烧毁容器的各种方法)
在 Spring 中,可以通过以下几种方式来指定 Bean 的初始化方法:
1. 使用 @PostConstruct 注解
2. 实现 InitializingBean 接口的 afterPropertiesSet() 方法
3. 在 xml 配置文件中使用 init-method 属性指定
烧毁容器的方法:
1. 使用 @PreDestory 注解
2. 实现 DisposableBean 接口的 destroy 方法
3. 在 xml 配置文件中使用 destroy-method 属性指定
实例化 和 属性赋值 对应 构造方法 和 setter 方法 的注入。而 初始化 和 烧毁 是用户可以或许自界说扩展的两个阶段,如,可以在实例化之后,类加载之进步行自界说事件处理
其执行流程如下:
接下来,我们就来通过代码观察一下 Bean 的生命周期
- @Component
- public class BeanLifeComponent implements BeanNameAware {
- private Student student;
- public BeanLifeComponent() {
- System.out.println("执行构造函数...");
- }
- // setter 方法注入
- @Autowired
- public void setStudent(Student student) {
- this.student = student;
- System.out.println("执行 setter 方法...");
- }
- @Override
- public void setBeanName(String name) {
- System.out.println("执行 setBeanName 方法 " + name);
- }
- // 初始化
- @PostConstruct
- public void init() {
- System.out.println("初始化...");
- }
- // 使用
- public void use() {
- System.out.println("执行 use 方法...");
- }
- // 销毁前执行方法
- @PreDestroy
- public void destroy() {
- System.out.println("执行 destroy 方法...");
- }
- }
复制代码 进行测试:
- @SpringBootTest
- class SpringBeanApplicationTests {
- @Autowired
- private ApplicationContext context;
- @Test
- void beanLife() {
- BeanLifeComponent bean = context.getBean(BeanLifeComponent.class);
- bean.use();
- }
- }
复制代码 运行并观察效果:
可以看到,先执行了 构造方法,为其分配了内存空间,接着执行了 setter 方法,进行了属性赋值,然后再执行了 关照方法(如 setBeanName),接下来,执行 初始化方法(@PostConstruct)
执行完初始化方法之后,此时才进行启动(Started SpringBeanApplicationTests in 1.4 seconds (process running for 2.214))
此时,执行了 use 方法(bean.use())
最后,进行烧毁(@PreDestroy)
我们通过源码来进一步看 Bean 的实例化、属性赋值 以及 初始化过程:
先找到 AbstractAutowireCapableBeanFactory 类:
AbstractAutowireCapableBeanFactory 继承自 AbstractBeanFactory,并实现了 AutowireCapableBeanFactory 接口,重要负责 实例化 bean、属性填充、调用 Bean 的初始化方法等
接着,在 AbstractAutowireCapableBeanFactory 中找到 createBean 方法:
createBean 的重要功能是创建一个 bean 实例、进行属性注入、进行后置处理等
接着,我们在 createBean 方法中找到 doCreateBean 方法:
检察 doCreateBean 方法:
在 doCreateBean 方法中,会调用 createBeanInstance 方法,创建指定的 bean
接着,会调用 populateBean 方法,完成 bean 的属性赋值:
完成 属性赋值之后,就会调用 initializeBean 方法,进行初始化
这三个方法也就与三个声明周期阶段一一对应:
createBeanInstance -> 实例化
populateBean -> 属性赋值
initializeBean -> 初始化
createBeanInstance :
在 createBeanInstance 中,会根据 bean 的配置(如 构造器参数、工厂方法等)来创建 Bean 实例
populateBean :
在 populateBean 中,会对 bean 的属性进行填充,将各个属性注入
initializeBean:
在 initializeBean 方法中,会先执行 invokeAwareMethods 方法,也就是 BeanNameAware、BeanClassLoaderAware 和 BeanFactoryAware 方法:
接着是 applyBeanPostProcessorsBeforeInitialization 方法:
applyBeanPostProcessorsBeforeInitialization 的作用是在 Bean 的初始化阶段之前,调用全部 BeanPostProcessor 实现类的 postProcessBeforeInitialization方法,从而对 Bean 实例进行进一步的修改或增强,如:修改 Bean 的属性、对 Bean 进行署理、记载日志等
也就是说,applyBeanPostProcessorsBeforeInitialization 允许我们在 Bean 完成依赖注入之后、正式初始化之前,对 Bean 进行处理和修改,也就为我们提供了在 Bean 初始化之前执行逻辑的机会,比方 AOP 增强、属性设置、日志记载等
然后是 invokeInitMethods 方法:
在 invokeInitMethods 方法中,重要是执行 bean 的初始化逻辑
bean instanceof InitializingBean:判断当前 Bean 是否实现了 InitializingBean 接口,若实现了该接口,调用 afterPropertiesSet 方法
getInitMethodNames:获取指定的初始化方法名称
invokeCustomInitMethod:调用指定的初始化方法
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |