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