Spring中Bean的作用域、实例化方式、生命周期、循环依赖问题
- 一、Bean的作用域
- 1.singleton
- 2.prototype
- 3.其他scope值
- 二、Bean的实例化方式
- 1.通过构造方法实例化
- 2.通过简单工厂模式实例化
- 3.通过factory-bean实例化
- 4.通过FactoryBean接口实例化
- 5.BeanFactory和FactoryBean的区别
- (1)BeanFactory
- (2)FactoryBean
- 三、Bean的生命周期
- 1.什么是Bean的生命周期
- 2.为什么要知道Bean的生命周期
- 3.Bean的生命周期之5步
- 4.Bean生命周期之7步
- 5.Bean生命周期之10步
- 6.Bean的作用域不同,管理方式不同
- 7.自己new的对象如何让Spring管理
- 四、Bean的循环依赖问题
- 1.什么是Bean的循环依赖
- 2.singleton下的set注入产生的循环依赖
- 3. prototype下的set注入产生的循环依赖
- 4.singleton下的构造注入产生的循环依赖
- 5.Spring解决循环依赖的机理
一、Bean的作用域
1.singleton
- 默认情况下,Spring的IoC容器创建的Bean对象是单例的。
- 默认情况下,Bean对象的创建是在初始化Spring上下文的时候就完成了。
2.prototype
- 如果想让Spring的Bean对象以多例的形式存在,可以在bean标签中指定scope属性的值为:prototype,这样Spring会在每一次执行getBean()方法的时候创建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.xsd">
<bean id="sb" class="com.gdb.spring6.beans.SpringBean" scope="prototype" />
</beans>
3.其他scope值
- scope属性的值不止两个,它一共包括8个选项:
- singleton:默认的,单例。
- prototype:原型。每调用一次getBean()方法则获取一个新的Bean对象。或每次注入的时候都是新对象。
- request:一个请求对应一个Bean。仅限于在WEB应用中使用。
- session:一个会话对应一个Bean。仅限于在WEB应用中使用。
- global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
- application:一个应用对应一个Bean。仅限于在WEB应用中使用。
- websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
- 自定义scope:很少使用。
二、Bean的实例化方式
- Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的是:更加灵活)
1.通过构造方法实例化
- 参考我的博客====>Spring对IoC的是实现中的Spring的第一个程序。
2.通过简单工厂模式实例化
- 第一步:定义一个Bean
package com.gdb.spring6.bean;
public class Vip {
}
- 第二步:编写简单工厂模式当中的工厂类
package com.gdb.spring6.bean;
public class VipFactory {
public static Vip get(){
return new Vip();
}
}
- 第三步:在Spring配置文件中指定创建该Bean的方法(使用factory-method属性指定)
<bean id="vipBean" class="com.gdb.spring6.bean.VipFactory" factory-method="get"/>
3.通过factory-bean实例化
- 这种方式本质上是:通过工厂方法模式进行实例化。
- 第一步:定义一个Bean
package com.gdb.spring6.bean;
public class Vip {
}
- 第二步:定义具体工厂类,工厂类中定义实例方法
- 在这里可以在创建Bean的前后进行加工处理。
package com.gdb.spring6.bean;
public class VipFactory {
public Vip get(){
return new Vip();
}
}
- 第三步:在Spring配置文件中指定factory-bean以及factory-method
<bean id="vipFactory" class="com.gdb.spring6.bean.VipFactory"/>
<bean id="vipBean" factory-bean="vipFactory" factory-method="get"/>
4.通过FactoryBean接口实例化
- 以上的第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
- 在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。
- factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
- 第一步:定义一个Bean
package com.gdb.spring6.bean;
public class Vip {
}
- 第二步:编写一个类实现FactoryBean接口
package com.gdb.spring6.bean;
import org.springframework.beans.factory.FactoryBean;
public class VipFactoryBean implements FactoryBean<Vip> {
@Override
public Vip getObject() throws Exception {
return new Vip ();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
// true表示单例
// false表示原型
return true;
}
}
- 第三步:在Spring配置文件中配置FactoryBean
<bean id="vipBean" class="com.gdb.spring6.bean.VipFactoryBean"/>
- FactoryBean在Spring中是一个接口。被称为“工厂Bean”。“工厂Bean”是一种特殊的Bean。所有的“工厂Bean”都是用来协助Spring框架来创建其他Bean对象的。
- 其实FactoryBean就是一个抽象工厂。
- 通过FactoryBean这个工厂Bean主要是想对普通Bean进行加工处理。
5.BeanFactory和FactoryBean的区别
(1)BeanFactory
- Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”,在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。
- BeanFactory是工厂。
(2)FactoryBean
- FactoryBean:它是一个Bean,是一个能够
辅助Spring
实例化其它Bean对象的一个Bean。 - 在Spring中,Bean可以分为两类:
- 第一类:普通Bean
- 第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)
三、Bean的生命周期
1.什么是Bean的生命周期
- Spring其实就是一个管理Bean对象的工厂。它负责对象的创建,对象的销毁等。
- 所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。
- 什么时候创建Bean对象?
- 创建Bean对象的前后会调用什么方法?
- Bean对象什么时候销毁?
- Bean对象的销毁前后调用什么方法?
2.为什么要知道Bean的生命周期
- 其实生命周期的本质是:在哪个时间节点上调用了哪个类的哪个方法。
- 我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。
- 只有我们知道了特殊的时间节点都在哪,到时我们才可以确定代码写到哪。
- 我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。
3.Bean的生命周期之5步
- Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。
- Bean生命周期可以粗略的划分为五大步:
- 第一步:实例化Bean
- 第二步:Bean属性赋值
- 第三步:初始化Bean
- 第四步:使用Bean
- 第五步:销毁Bean
- 需要注意的:
- 第一:只有正常关闭spring容器,bean的销毁方法才会被调用。
- 第二:ClassPathXmlApplicationContext类才有close()方法。
- 第三:配置文件中的init-method指定初始化方法。destroy-method指定销毁方法。(初始化和销毁方法,需要自己在Bean中编写,然后在配置文件中进行配置)
4.Bean生命周期之7步
- 在以上的5步中,第3步是初始化Bean,如果你还想在初始化前和初始化后添加代码,可以加入“Bean后处理器”。
- 编写一个类实现BeanPostProcessor类,并且重写before和after方法:
package com.gdb.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的before方法执行,即将开始初始化");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的after方法执行,已完成初始化");
return bean;
}
}
- 在spring.xml文件中配置“Bean后处理器”:
<!--配置Bean后处理器。这个后处理器将作用于当前配置文件中所有的bean。-->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
- 一定要注意:
在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。
5.Bean生命周期之10步
6.Bean的作用域不同,管理方式不同
- Spring 根据Bean的作用域来选择管理方式。
- 对于singleton作用域的Bean,Spring 能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁;
- 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
*对于 prototype 作用域Spring容器管理 Bean生命周期的前八步。
7.自己new的对象如何让Spring管理
- 有些时候可能会遇到这样的需求,某个java对象是我们自己new的,然后我们希望这个对象被Spring容器管理,怎么实现?
package com.gdb.spring6.test;
import com.gdb.spring6.bean.User;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
public class RegisterBeanTest {
@Test
public void testBeanRegister(){
// 自己new的对象
User user = new User();
System.out.println(user);
// 创建 默认可列表BeanFactory 对象
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 注册Bean
factory.registerSingleton("userBean", user);
// 从spring容器中获取bean
User userBean = factory.getBean("userBean", User.class);
System.out.println(userBean);
}
}
四、Bean的循环依赖问题
1.什么是Bean的循环依赖
- A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
2.singleton下的set注入产生的循环依赖
- 在singleton + setter模式注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。
- 主要原因是:在这种模式下 Spring 对 Bean 的管理主要分为清晰的两个阶段:
- 第一阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”【不等属性赋值曝光】。
- 第二阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法。)
- 核心解决方案是:
实例化对象和对象的属性赋值分为两个阶段来完成的。
- 主要原因是:在这种模式下 Spring 对 Bean 的管理主要分为清晰的两个阶段:
3. prototype下的set注入产生的循环依赖
- 当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现
BeanCurrentlyInCreationException
异常。 - 如果其中一个是singleton,另一个是prototype,是没有问题的。
4.singleton下的构造注入产生的循环依赖
- 因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
5.Spring解决循环依赖的机理
- 总结:
Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。