Bean的作用域和生命周期

打印 上一主题 下一主题

主题 865|帖子 865|积分 2595

目次

Bean 的作用域
单例作用域
多例作用域
请求作用域
会话作用域
全局作用域
Bean 的生命周期


Bean 的作用域

在 Spring IoC和DI-CSDN博客 中,我们学习了 Spring 是如何资助我们管理对象的:
(1)通过 @Controller、@Service、@Repository、@Component、@Configuration、@Bean 来声明 Bean
(2)通过 ApplicaationContext 或 BeanFactory 来获取Bean
(3)通过 属性注入、Setter 方法注入 或 构造方法注入等为程序注入所依赖的 Bean 对象
比方:
  1. public class Student {
  2.     private int id;
  3.     private String name;
  4.     public int getId() {
  5.         return id;
  6.     }
  7.     public void setId(int id) {
  8.         this.id = id;
  9.     }
  10.     public String getName() {
  11.         return name;
  12.     }
  13.     public void setName(String name) {
  14.         this.name = name;
  15.     }
  16. }
复制代码
使用 @Bean 声明 bean,将 bean 存储在 Spring 容器中:
  1. @Component
  2. public class StudentBeanConfig {
  3.     @Bean
  4.     public Student student() {
  5.         return new Student();
  6.     }
  7. }
复制代码
从 Spring 容器中获取 Bean:
  1. @SpringBootApplication
  2. public class SpringBeanApplication {
  3.     public static void main(String[] args) {
  4.         ApplicationContext context = SpringApplication.run(SpringBeanApplication.class, args);
  5.         Student student = context.getBean(Student.class);
  6.         System.out.println(student);
  7.     }
  8. }
复制代码
也可以直接注入 ApplicationContext,获取 Spring 容器,从而获取 Bean:
  1. @SpringBootTest
  2. class SpringBeanApplicationTests {
  3.     @Autowired
  4.     private ApplicationContext context;
  5.     @Test
  6.     void contextLoads() {
  7.         Student student = context.getBean(Student.class);
  8.         System.out.println(student);
  9.     }
  10. }
复制代码
从 Spring 容器中多次获取 Bean:
  1. @SpringBootTest
  2. class SpringBeanApplicationTests {
  3.     @Autowired
  4.     private ApplicationContext context;
  5.     @Test
  6.     void contextLoads() {
  7.         Student student = context.getBean(Student.class);
  8.         System.out.println(student);
  9.         Student student1 = context.getBean(Student.class);
  10.         System.out.println(student1);
  11.     }
  12. }
复制代码
运行并观察效果:

可以看到,两次输出的 bean 地址值是相同的,也就是说,每次从 Spring 容器中取出的对象是同一个
这样的模式也称为单例模式(一个类只有一个实例,多次创建也不会创建出多个实例)
默认情况下,Spring 容器中的 bean 都是单例的,这种行为模式,我们就称为 Bean 的作用域
 Bean 的作用域是指 Bean 在Spring 框架中的某种行为模式
就如上述单例作用域,表现的是 Bean 在整个 Spring 中只有一份,是全局共享的。在一个方法中修改了它的值,其他方法读取到的就是被修改后的值:
  1. @SpringBootTest
  2. class SpringBeanApplicationTests {
  3.     @Autowired
  4.     private ApplicationContext context;
  5.     @Test
  6.     void contextLoads() {
  7.         Student student = context.getBean(Student.class);
  8.         student.setId(1);
  9.         System.out.println(student.getId());
  10.         student.setId(2);
  11.         System.out.println(student.getId());
  12.         Student student1 = context.getBean(Student.class);
  13.         System.out.println(student.getId());
  14.     }
  15. }
复制代码
运行效果:

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 的作用域: 
  1. @Component
  2. public class StudentBeanConfig {
  3.     @Bean
  4.     @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
  5.     public Student singleStudent() {
  6.         return new Student();
  7.     }
  8.     @Bean
  9.     @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  10.     public Student prototypeStudent() {
  11.         return new Student();
  12.     }
  13.     @Bean
  14.     @RequestScope
  15.     public Student requestStudent() {
  16.         return new Student();
  17.     }
  18.     @Bean
  19.     @SessionScope
  20.     public Student sessionStudent() {
  21.         return new Student();
  22.     }
  23.    
  24.     @Bean
  25.     @ApplicationScope
  26.     public Student applicationStudent() {
  27.         return new Student();
  28.     }
  29. }
复制代码
 在 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 获取到的对象是否相同
  1. @RestController
  2. @RequestMapping("/student")
  3. public class StudentController {
  4.     @Resource(name = "singleStudent")
  5.     private Student singleStudent;
  6.     @Resource(name = "prototypeStudent")
  7.     private Student prototypeStudent;
  8.     @Resource(name = "requestStudent")
  9.     private Student requestStudent;
  10.     @Resource(name = "sessionStudent")
  11.     private Student sessionStudent;
  12.     @Resource(name = "applicationStudent")
  13.     private Student applicationStudent;
  14.     @Autowired
  15.     private ApplicationContext context;
  16.     @RequestMapping("/single")
  17.     public String single() {
  18.         Student student1 = (Student) context.getBean("singleStudent");
  19.         return "student: " + singleStudent + "</br> contextStudent: " + student1;
  20.     }
  21.     @RequestMapping("/prototype")
  22.     public String prototype() {
  23.         Student student1 = (Student) context.getBean("prototypeStudent");
  24.         return "student: " + prototypeStudent + "</br> contextStudent: " + student1;
  25.     }
  26.     @RequestMapping("/request")
  27.     public String request() {
  28.         Student student1 = (Student) context.getBean("requestStudent");
  29.         return "student: " + requestStudent + "</br> contextStudent: " + student1;
  30.     }
  31.     @RequestMapping("/session")
  32.     public String session() {
  33.         Student student1 = (Student) context.getBean("sessionStudent");
  34.         return "student: " + sessionStudent + "</br> contextStudent: " + student1;
  35.     }
  36.     @RequestMapping("/application")
  37.     public String application() {
  38.         Student student1 = (Student) context.getBean("applicationStudent");
  39.         return "student: " + applicationStudent + "</br> contextStudent: " + student1;
  40.     }
  41. }
复制代码
我们让每个请求都获取两次 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 的生命周期
  1. @Component
  2. public class BeanLifeComponent implements BeanNameAware {
  3.     private Student student;
  4.     public BeanLifeComponent() {
  5.         System.out.println("执行构造函数...");
  6.     }
  7.     // setter 方法注入
  8.     @Autowired
  9.     public void setStudent(Student student) {
  10.         this.student = student;
  11.         System.out.println("执行 setter 方法...");
  12.     }
  13.     @Override
  14.     public void setBeanName(String name) {
  15.         System.out.println("执行 setBeanName 方法 " + name);
  16.     }
  17.     // 初始化
  18.     @PostConstruct
  19.     public void init() {
  20.         System.out.println("初始化...");
  21.     }
  22.     // 使用
  23.     public void use() {
  24.         System.out.println("执行 use 方法...");
  25.     }
  26.     // 销毁前执行方法
  27.     @PreDestroy
  28.     public void destroy() {
  29.         System.out.println("执行 destroy 方法...");
  30.     }
  31. }
复制代码
进行测试:
  1. @SpringBootTest
  2. class SpringBeanApplicationTests {
  3.     @Autowired
  4.     private ApplicationContext context;
  5.     @Test
  6.     void beanLife() {
  7.         BeanLifeComponent bean = context.getBean(BeanLifeComponent.class);
  8.         bean.use();
  9.     }
  10. }
复制代码
运行并观察效果:

可以看到,先执行了 构造方法,为其分配了内存空间,接着执行了 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 方法,也就是 BeanNameAwareBeanClassLoaderAware 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企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

自由的羽毛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表