你对Spring中的bean了解吗?都有哪些作用域(Scope)?
Spring 官方文档对 bean 的解释是:
In Spring, the objects that form the backbone of your application and that are managed by the Spring IOC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.
翻译 ->
在 Spring 中,构成应用程序主干并由Spring IOC容器(ApplicationContext)管理的对象称为bean。bean是一个由Spring IOC容器实例化、组装(assembled)和管理的对象。
关键信息 ->
(1)bean是对象,一个或者多个,不限定个数;
(2)bean由Spring中一个叫IOC的容器管理;
(3)应用程序由bean构成;
Spring中的Bean有五种作用域(Scope):
(1) Singleton:一个Spring容器中只有一个Bean的实例,此为Spring的默认配置,全容器共享一个实例。
(2)Prototype:每次调用新建一个新的Bean实例。
(3)Request:Web项目中,给每一个http request新建一个Bean实例。
(4)Session:Web项目中,给每一个http session新建一个Bean实例。(5)GlobalSession:只在portal应用中有用,给每一个global http session新建一个Bean实例。
补充:Scope描述的是Spring容器如何创建Bean的实例的。
Spring中将一个类声明为Bean的注解和注入bean的注解有哪些?
声明为Bean的注解:
(1)@Component:通用的注解,可标注任意类为spring组件。如果一个Bean没有明确的角色,可以使用@Component注解标注。
(2)@Service:在业务逻辑层(service层)使用。
(3)@Repository:在数据访问层(dao层)使用。
(4)@Controller:在展现层(MVC->Spring MVC)使用,主要用于接收用户请求并调用Service层返回数据给前端页面。
注入Bean的注解(一般情况下通用):
(1)@Autowired:Spring提供的注解。
(2)@Inject:JSR-330提供的注解。
(3)@Resource:JSR-250提供的注解。
@Component和@Bean有什么区别呢?
(1)作用对象不同:@Component作用于类,@Bean作用于方法。
(2)@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(使用@ComponentScan注解定义要扫描的路径从中找出识别了需要装配的类自动装配到spring的Bean容器中)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,@Bean告诉Spring这是某个类的实例,当我需要用它的时候还给我。
(3)@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册Bean,比如第三方库中的类。
Spring是单例还是多例,怎么修改?
Spring的bean默认是单例的(sigleton)可以修改为多例(prototype),在此bean节点中添加一个属性,scope="prototype";
例如<bean id="xxx" class="全类名" scope="prototype"></bean>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!--
scope属性控制当前bean的创建模式:
singleton:则当前bean处在单例模式中,默认就是此模式
prototype:则当前bean处在多例模式中
-->
<bean id="cart" class="com.spring.beans.Cart" scope="prototype"></bean>
</beans>
以下针对Spring对单例,多例进行说明
在Spring中,bean的Scope常被定义的两种模式:prototype(多例)和singleton(单例)。
singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
此外,singleton类型的bean定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活,典型单例模式,如同servlet在web容器中的生命周期。
prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。
spring容器在输出prototype的bean对象时,会每次都重新生成一个新的对象给请求方,虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不在拥有当前对象的引用,请求方需要自己负责当前对象后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回请求方该对象的一个新的实例之后,就由这个对象“自生自灭”。
为什么用单例或者多例?何时用?
1)用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
2)用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
3)当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例。
Spring中的Bean是线程安全的吗?
Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring本身并没有提供线程安全的策略,对于每个bean的线程安全问题,根本原因是每个bean自身的设计,因此是否线程安全需要由开发者自己编写解决线程安全问题的代码。
无状态的对象,不管单例还是多例都是线程安全的。无状态的对象自然也就不会因为多个线程的交替调度而破坏自身状态导致线程安全问题。无状态对象包括我们经常使用的DO、DTO、VO这些只作为数据的实体模型的贫血对象,还有Service、DAO和Controller,这些对象并没有自己的状态,它们只是用来执行某些操作的。例如,每个DAO提供的函数都只是对数据库的CRUD,而且每个数据库Connection都作为函数的局部变量(局部变量是在用户栈中的,而且用户栈本身就是线程私有的内存区域,所以不存在线程安全问题),用完即关(或交还给连接池)。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用Synchronized、Lock、CAS等这些实现线程同步的方法了。
补充说明:
什么是有状态和无状态对象?
1、有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
2、无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。
Bean的生命周期
对于SpringBean来说,并不是启动阶段就会触发Bean的实例化,只有当客户端通过显式或者隐式的方式调用BeanFactory的getBean()方法时,它才会触发该类的实例化方法。当然对于BeanFactory来说,也不是所有的getBean()方法都会实例化Bean对象,例如作用域为singleton时,只会在第一次,实例化该Bean对象,之后会直接返回该对象。但如果使用的是 ApplicationContext 容器,则会在该容器启动的时候,立即调用注册到该容器所有 Bean 的实例化方法。
完整版流程如下:
简化翻译版:
1.instantiate[ɪns'tænʃɪeɪt] bean 实例化bean ,Bean容器找到配置文件中Spring Bean的定义,利用Java反射机制创建一个Bean的实例。
2.populate properties 封装属性(注入对象属性),如果涉及一些属性值,利用set()方法设置一些属性值。
3.执行Aware
(1)如果Bean实现BeanNameAware 接口,调用setBeanName()方法,传入Bean的名称。
(2)如果 Bean 实现了BeanFactoryAware 接口,调用 setBeanFactory ()方法,将 BeanFactory 容器实例传入;
(3)如果Bean实现了ApplicationContextAware接口,调用setApplicationContext()方法。
4.如果存在类实现 BeanPostProcessor(前置处理),执行postProcessBeforeInitialization()。
5.InitializingBean、init-method检查和执行
(1)如果Bean实现InitializingBean 接口,执行 afterPropertiesSet()方法。
(2)调用自定义的初始化方法init-method.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
6.如果存在类实现 BeanPostProcessor(后置处理) ,执行postProcessAfterInitialization().
7.Bean准备使用,执行业务处理。
8.执行销毁方法
(1)当要销毁Bean的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
(2)调用自定义的销毁方法destroy-method,当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
源码分析待补充
常见面试题?
(1) Spring Bean 的作用域有哪些?它的注册方式有几种?
(2) 什么是同名 Bean?它是如何产生的?应该如何避免?
(3) Bean 的生命周期?
(4) Spring是单例还是多例,怎么修改?
(5) 为什么用单例或者多例?何时用?
(6) Spring中的Bean是线程安全的吗?
(7) Spring中将一个类声明为Bean的注解和注入bean的注解有哪些?
(8) @Component和@Bean有什么区别呢?
参考/好文
(1) 书籍 -- SpringBoot实战 -- 汪云飞 编著
(2) 聊一聊Spring中的线程安全性 --
https://juejin.im/post/5a0045ef5188254de169968e
(3)《今天面试了吗》 - Spring
https://juejin.im/post/5e6d993cf265da575b1bd4af
(4) 拉钩课程 -- Java源码剖析
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59