Bean的作用域和生命周期

互联架构唠唠嗑 2024-04-07 08:20:32

Spring Framework的主要功能是用来存储和读取Bean,Bean于Spring框架的地位可见一斑。本节的目的就是从作用域和生命周期的角度更加深入了解一下Bean对象。

1. Bean的作用域

首先我们来学习一下Bean的作用域,源码位置:bean_scope

1.1 作用域的定义

作用域一词在学习JavaSE的时候就有接触过,指的是源代码中定义变量的某个区域,与JavaSE中定义的作用域不同,Bean 的作用域是指Bean在Spring整个框架中的某种行为模式,比如Spring中默认的Ben作用域:singleton单例作用域,指的就是在整个Spring中只有一份。

Spring有6种作用域:

singleton: 单例作用域prototype: 原型作用域(多例)request: 请求作用域session: 会话作用域application: 全局作用域websocket: HTTP WebSocket 作用域

后 4 种状态是Spring MVC 中的值, 在普通的 Spring 项目中只有前两种

【问题】默认作用域的不足

Spring中Bean的默认作用域是singleton单例作用域,在业务中可能遇到下面这种场景:

首先在名为model的包中定义User,用来传输User的数据:

public User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + ''' + ", age=" + age + '}'; }}

创建一个bean包,并定义一个UserBean,用于初始化User并将User存入Spring容器中:

@Componentpublic UserBean { @Bean(name = "user") public User getUserBean() { User user = new User(); user.setName("zhangsan"); user.setAge(20); return user; }}

在controller包中创建UserController1和UserController2分别代表两个业务

程序员A在业务中需要临时创建临时的User对象并且需要进行修改,出于无心之举,创建的临时对象user2直接浅拷贝了Bean,于是修改了Bean的数据,然后将代码推送到仓库:

@Controllerpublic UserController1 { @Autowired private User user; public void doController() { System.out.println("Do UserController1"); System.out.println("从Spring中取出user:" + user); //创建的临时对象user2直接浅拷贝了Bean User user2 = user; user2.setName("lisi"); user2.setAge(18); System.out.println("修改后的数据" + user2); }}

程序员B拉取了新的代码,并且要将 UserController2 给其他业务部门交付,并展示,预期中User的数据是User{name='zhangsan', age=20}:

@Controllerpublic UserController2 { @Autowired private User user; public void doController() { System.out.println("Do UserController2"); System.out.println("从Spring中取出user:" + user); }}

程序员B展示他的业务代码并一顿鼓吹他是如何拿到这么一个User{name='zhangsan', age=20}的数据:

public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml"); //UserController1业务 UserController1 userController1 = context.getBean("userController1", UserController1.class); userController1.doController(); System.out.println("==========================="); //UserController2业务 UserController2 userController2 = context.getBean("userController2", UserController2.class); userController2.doController();}

但是他并不知道代码被修改,在展示的过程中直接傻眼了,运行结果如下:

Do UserController1从Spring中取出user:User{name='zhangsan', age=20}修改后的数据User{name='lisi', age=18}===========================Do UserController2从Spring中取出user:User{name='lisi', age=18}

1.2 Bean的 6 种作用域

上述问题的解决方案就是设置 Bean 的作用域,在说明设置 Bean 的作用域的方法前,先来介绍下 Bean 的 6 种作用域的定义。

1) singleton作用域(单例)

官方说明:(Default)Scopes a single bean definition to a single object instance for each Spring IoC container.

描述: 该作用域下的Bean在IoC容器中只存在一个实例:singleton作用域是Spring中Bean的默认作用域,singleton作用域指的是单例作用域,这里的单例和常规设计模式中的单例又稍微一些不一样,常规设计模式中的单例模式指的是一个类只能对应唯一的实例对象;而 Bean 的单例作用域表示的是 Bean 对象在Spring中只有一份,它是全局共享的。

场景: 通常无状态的Bean使用该作用域,无状态表示Bean的属性不需要更新。

2) prototype作用域 (原型)

官方说明: Scopes a single bean definition to any number of object instances.

描述: 每次该作用域下的获取Bean的请求都会创建新的实例:这种方式对比singleton作用域,会频繁创建实例,因此开销较大。

场景: 通常有状态的Bean使用该作用域,如前面所提到的例子,就可以用到该作用域。

3) request作用域

官方说明: Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTPrequest has its own instance of a bean created off the back of a single bean definition. Only valid in thecontext of a web-aware Spring ApplicationContext.

描述: 每次HTTP请求会创建新的Bean实例,它的生命周期和一个HTTP的生命周期相同。

场景: 一次HTTP的请求和响应的共享Bean,如在处理一个HTTP请求的过程中,可能有多个组件或步骤需要访问或修改同一份数据,此时就可以用上request作用域。

备注: 限定Spring MVC中使用

4) session作用域

官方说明: Scopes a single bean definition to the lifecycle of an HTTP Session. Only a web-aware Spring ApplicationContext.

描述: 在一个http session(会话)中,定义一个Bean实例,它的生命周期和会话的生命周期相同。

场景: 用户会话的共享Bean,常用于在用户的整个会话过程中共享数据,比如:记录一个用户的登录信息。

备注: 限定Spring MVC中使用

5) application作用域

官方说明: Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.

描述: 一个http servlet context共享的Bean,它们在服务器开始执行服务时创建,并持续存在直到服务器关闭。

场景: Web应用的上下文信息,比如:记录一个应用的共享信息

备注: 限定Spring MVC中使用

6) websocket作用域

官方说明: Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of web-aware Spring ApplicationContext

描述: 在一个HTTP WebSocket的生命周期中,定义一个Bean实例

对WebSocket的解释: HTTP协议是基于请求/响应模式的,在客户端发起请求,服务端返回响应后,连接就结束了。WebSocket可以看作是可以实现长连接的HTTP的升级,弥补了HTTP协议无法进行长连接的缺点,一次WebSocket的连接可以看作是Websocket会话。

场景: WebSocket的每次会话中,开始时初始化Bean,直到WebSocket结束都是同一个Bean。

备注: 限定Spring MVC中 使用

1.3 【方案】设置作用域

使用@Scope标签就可以用来声明 Bean 的作用域,如下面代码:

@Componentpublic UserBean { @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Bean(name = "user") public User getUserBean() { User user = new User(); user.setName("zhangsan"); user.setAge(20); return user; }}

@Scope标签既可以修饰方法也可以修饰类,它有两种设置方式:

直接设置值:@Scope("prototype")使用 ConfigurableBeanFactory(推荐):@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

其实SCOPE_PROTOTYPE就是 ConfigurableBeanFactory 的一个字段:

在设置了prototype作用域后,终于能够如预期拿到结果:

Do UserController1从Spring中取出user:User{name='zhangsan', age=20}修改后的数据User{name='lisi', age=18}===========================Do UserController2从Spring中取出user:User{name='zhangsan', age=20}

2 Spring主要执行流程2.1 容器初始化ApplicationContext context = new ClassPathXmlApplicationContext("bean-lifecycle.xml");

2.2 Bean的实例化

读取xml配置文件中配置的所有 Bean 对象以及xml配置文件所配置的扫描路径下添加六大注解的Bean,通过反射实例化Bean(包括分配内存空间和调用构造方法)

<!-- xml中直接配置的Bean --><bean id="userXml">

2.3 Bean的依赖注入

在依赖注入前,对象仍然是一个原生的状态,并没有进行依赖注入,值得一提的依赖注入这一步并不一定是在2. Bean的实例+初始化之后,如果是构造方法注入的话,会在2. Bean的实例+初始化的初始化的过程中注入所需依赖。

扩展:如果Bean所需要依赖注入的对象没有完全初始化好,会先去完全初始化好(也就是依赖注入)该对象,再注入该Bean

2.4 存入Spring容器中

Spring容器会将这个已经完全初始化好的Bean存入IoC容器中。IoC容器通常是一个Map结构的实现,它使用Bean的名称或ID作为键,Bean的实例作为值。

2.5 使用Bean

应用程序可以通过依赖注入或依赖查找机制来获取bean的引用,并调用其方法。

2.6 销毁Spring容器

Spring容器关闭时,会触发bean的销毁过程。

3. Bean的生命周期

源码位置:bean-lifecycle

Bean的生命周期分为以下5大部分:

实例化 Bean(为Bean分配内存空间)设置属性(Bean的依赖注入,如@Autowired)Bean 初始化 执行各种通知(xxxAware的接口方法) 执行初始化方法(各种初始化方法执行顺序如下) @PostConstruct定义的初始化方法 判断是否为InitializingBean(调用afterPropertiesSet) xml中定义的init-method方法使用Bean销毁Bean 销毁Bean前的各种方法,如@PreDestroy,DisposableBean接口方法,destroy-method.

3.1 代码验证

User类,为了演示依赖注入的时机:

@Componentpublic User implements BeanPostProcessor {}

BeanLifeComponent类,演示了生命周期:

@Componentpublic BeanLifeComponent implements BeanNameAware, InitializingBean { private User user; //构造方法 public BeanLifeComponent() { System.out.println("执行构造方法"); } //setter依赖注入 @Autowired public void setUser(User user) { this.user = user; System.out.println("依赖注入"); } //BeanNameAware的通知方法 @Override public void setBeanName(String name) { System.out.println("执行了BeanName通知方法,name="+name); } //初始化方法 @PostConstruct public void initByAnnotation() { System.out.println("执行@PostConstrut修饰的init方法"); } //初始化方法 @Override public void afterPropertiesSet() throws Exception { System.out.println("执行InitializingBean接口的afterPropertiesSet方法"); } //初始化方法 public void initByXml() { System.out.println("执行xml中定义的init方法"); } //使用Bean public void use() { System.out.println("使用use方法"); } //销毁前调用的方法 @PreDestroy public void destroy() { System.out.println("执行@PreDestroy修饰的destroy方法"); }}

xml中的配置文件:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="beanLifeComponent" >

运行结果:

执行构造方法依赖注入执行了BeanName通知方法,name=beanLifeComponent执行@PostConstrut修饰的init方法执行InitializingBean接口的afterPropertiesSet方法执行xml中定义的init方法使用use方法执行@PreDestroy修饰的destroy方法

作者:Iris761链接:https://juejin.cn/post/7353544284417671220

0 阅读:0

互联架构唠唠嗑

简介:感谢大家的关注