一、Bean的生命周期:
Spring中的一个Bean生命周期总体分为以下几个阶段:
二、细化Bean的生命周期:
从上面可以看出,可初步分为四个阶段:
- Bean的实例化阶段(通过反射创建对象)
- Bean的设置属性阶段(属性值非自动装配)
- Bean的初始化阶段(如数据源赋值、校验属性)
- Bean的销毁阶段(ioc容器销毁关闭,关闭数据源)
-
在设置属性阶段后,postProcessBeforeInitialization方法执行前,会执行很多Aware类型的接口,这种类型接口作用是加载资源到Spring容器中,让bean获取Spring容器中的服务。
-
在初始化阶段,有个特别重要的接口BeanPostProcessor(Bean处理器),在初始化前、后调用:
-
初始化方式有三个,分别是:
1.InitializingBean的afterPropertiesSet方法
2.@PostConstruct注解标注的方法
3.配置的init-method -
容器销毁的方式有三个,分别是:
1.@PreDestroy注解标注的方法
2.DisposableBean接口的destroy()方法
3.配置的destroy-method
三、总结Bean生命周期:
- Bean容器找到Spring配置文件中Bean的定义;
- Bean容器利用java 反射机制实例化Bean;
- Bean容器为实例化的Bean 设置属性值;
- 如果Bean实现了BeanNameAware接口,则执行setBeanName方法;
- 如果Bean实现了BeanClassLoaderAware接口,则执行setBeanClassLoader方法;
- 如果Bean实现了BeanFactoryAware接口,则执行setBeanFactory方法;
- 如果Bean实现了xxxAware接口…;
- 如果Bean实现了ApplicationContextAware接口,则执行setApplicationContext方法;
- 如果加载了BeanPostProcessor相关实现类,则执行postProcessBeforeInitialization方法(前置处理器);
- 如果Bean定义初始化方法(PostConstruct注解、配置init-method、实现了InitializingBean接口),则执行定义的初始化方法;
- 如果加载了BeanPostProcessor相关实现类,则执行postProcessAfterInitialization方法(后置处理器);
- 当要销毁这个Bean时,如果自定义了销毁方法(PreDestroy注解、配置destroy-method、实现了DisposableBean接口),则执行定义的销毁方法。
四、Bean的作用域:
spring 支持 5 种作用域,如下:
-
singleton:唯一bean实例,Spring中的bean默认都是单例的。
-
prototype:每次请求都会创建一个新的bean实例。
-
request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
-
session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
-
global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
五、Bean是线程安全的吗?
Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。
结合上面提到的Spring bean 的作用域(scope)
-
对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
-
对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
- 有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
- 无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
-
对于singleton作用域的有状态Bean(比如ModelAndView),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
-
同步机制:采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。
-
ThreadLocal:采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
六、Spring bean为什么默认是单例
(1)单例bean与原型bean的区别
-
如果一个bean被声明为单例的时候,在处理多次请求的时候在Spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。
-
但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。
1. 画图分析
2. 源码分析
生成bean时先判断单例的还是原型的
如果是单例的则先尝试从缓存里获取,没有在新创建
3. 结论:
&esmp;&esmp;单例的bean只有第一次创建新的bean 后面都会复用该bean,所以不会频繁创建对象原型的bean每次都会新创建。
(2)单例bean的优势
由于不会每次都新创建新对象所以有一下几个性能上的优势。
- 减少了新生成实例的消耗
新生成实例消耗包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
- 减少jvm垃圾回收
由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
- 可以快速获取到bean
因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
(3)单例bean的劣势
单例的bean一个很大的劣势就是他不能做到线程安全!由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
(4)总结
-
Spring 为啥把bean默认设计成单例?为了提高性能少创建实例垃圾回收缓存快速获取
-
单例有啥劣势?如果是有状态的话在并发环境下线程不安全
-
什么是有状态对象?什么是无状态对象?
- 有状态对象:有实例变量可以标志其对象所处的状态。(有实例变量的对象,有存储数据能力)- 白话:有属性的对象
- 无状态对象:无实例变量可以标志其对象所处的状态。(无实例变量的对象,无存储数据能力)- 白话:无属性的对象