目录
五、Bean的作用域
5.1singleton
5.2prototype
5.3其它scope
七、Bean的实例化方式
7.1通过构造方法实例化
7.2通过简单工厂模式实例化
7.3通过factory-实例化
7.4通过FactoryBean接口实例化
7.5BeanFactory和FactoryBean的区别
7.6注入自定义Date
八、Bean的生命周期
8.1Bean的生命周期之5步
8.2Bean生命周期之7步
8.3Bean生命周期之10步
8.4Bean的作用域不同,管理方式不同
8.5自己new的对象如何让Spring管理
九、Bean的循环依赖问题
9.1什么是Bean的循环依赖
9.2singleton下的set注入产生的循环依赖
9.3prototype下的set注入产生的循环依赖
9.4singleton下的构造注入产生的循环依赖
9.5Spring解决循环依赖的机理
SpringBean
package com.hhb.;
public class SpringBean {
public SpringBean() {
System.out.println("SpringBean的无参数构造方法执行了");
}
}
spring-scope.xml
Test
@Test
public void testBeanScope() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb);
SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb2);
SpringBean sb3 = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb3);
}
默认情况下,Spring的IoC容器创建的对象是单例的。
在Spring上下文初始化的时候实例化,每一次调用getBean()方法的时候,都返回那个单例的对象。
将的scope属性设置为prototype的时候,是多例的,spring上下文初始化的时候,并不会初始化这些prototype的。每一次调用getBean()方法的时候,实例化该对象。
scope属性的值不止两个,它一共包括8个选项。
singleton:默认的,单例。
prototype:原型。每调用一次getBean()方法则获取一个新的对象,或每次注入的时候都是新的对象。
request:一个请求对应一个。仅限于在WEB应用中使用。
session:一个会话对应一个。仅限于在WEB应用中使用。
global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范,servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
application:一个应用对应一个。仅限于在WEB应用中使用。
websocket:一个websocket生命周期对应一个。仅限于在WEB应用中使用。
自定义scope:很少使用。
Spring为提供了多种实例化方式,通常包括4种方式。(也就是说Spring中为对象的创建准备了多种方案,目的是:更灵活)
第一种:通过构造方法实例化
第二种:通过简单工厂模式实例化
第三种:通过factory-实例化
第四种:通过FactoryBean接口实例化
我们之前一直使用的就是这种方式,默认情况下,会调用的无参数构造方法。
SpringBean
package com.hhb.;
public class SpringBean {
public SpringBean() {
System.out.println("Spring !!!");
}
}
spring.xml
Test
@Test
public void Test1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
System.out.println(sb);
}
第一步:定义一个
package com.hhb.;
public class Star {
public Star() {
System.out.println("star!!!");
}
}
第二步:编写简单工厂模式当中的工厂类
package com.hhb.;
public class StarFactory {
//工厂类中有一个静态方法。
//简单工厂模式又叫做静态工厂方法模式
public static Star get() {
//这个Star对象最终实际上创建的时候还是我们负责new的对象
return new Star();
}
}
第三步:在Spring配置文件中指定创建该的方法(使用factory-method属性指定)
测试
@Test
public void Test2() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Star star = applicationContext.getBean("star", Star.class);
System.out.println(star);
}
这种方式的本质:通过工厂方法模式进行实例化。
第一步:定义一个
package com.hhb.;
public class Gun {
public Gun(){
System.out.println("Gun!!!");
}
}
第二步:定义具体工厂类,工厂类中定义实例方法
package com.hhb.;
public class GunFactory {
public Gun get() {
return new Gun();
}
}
第三步:在Spring配置文件中指定factory-以及factory-method
测试
@Test
public void Test3() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Gun gun = applicationContext.getBean("gun", Gun.class);
System.out.println(gun);
}
在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-和factory-method不需要指定了。
factory-会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
FactoryBean在Spring中是一个接口,被称为“工厂”。“工厂”是一种特殊的。所有的“工厂”都属用来协助Spring框架来创建其他对象。
第一步:定义一个
package com.hhb.;
public class Person {
public Person() {
System.out.println("Person!!!");
}
}
第二步:编写一个类 实现FactoryBean接口
package com.hhb.;
import org.springframework.beans.factory.FactoryBean;
public class PersonFactoryBean implements FactoryBean {
//PersonFactoryBean也是一个,只不过这个比较特殊,叫做工厂
//通过工厂这个特殊的可以获取一个普通的
@Override
public Person getObject() throws Exception {
//自己创建
return new Person();
}
@Override
public Class> getObjectType() {
return null;
}
//这个方法在接口中有默认实现,默认返回true,表示单例的
//如果想多例,直接将这个方法修改为 return false;即可
@Override
public boolean isSingleton() {
return true;
}
}
第三步:在Spring配置文件中配置FactoryBean
Test
@Test
public void Test4() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person person = applicationContext.getBean("person", Person.class);
System.out.println(person);
}
BeanFactory
Spring IoC容器的顶级对象,BeanFactory被翻译为“工厂”,在Spring的IoC容器中,”工厂“负责创建对象。
BeanFactory是工厂。
FactoryBean
FactoryBean:它是一个,是一个能够辅助Spring实例化其它对象的一个。
在Spring中,可以分为两类:
普通
工厂
Student
package com.hhb.;
import java.util.Date;
public class Student {
//java.util.Date 在Spring当中被当作简单类型,但是简单类型的话,注入的日期字符串格式有要求
//java.util.Date 在Spring当中也可以被当做非简单类型。
private Date birth;
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Student{" +
"birth=" + birth +
'}';
}
}
DateFactoryBean
package com.hhb.;
import org.springframework.beans.factory.FactoryBean;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateFactoryBean implements FactoryBean {
//DateFactoryBean 这个工厂协助Spring创建这个普通的:Date
private String strDate;
public DateFactoryBean(String strDate) {
this.strDate = strDate;
}
@Override
public Date getObject() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(strDate);
return date;
}
@Override
public Class> getObjectType() {
return null;
}
}
spring.xml
>
>
什么是生命周期?
Spring其实就是一个管理对象的工厂。它负责对象的创建、销毁等。
所谓的生命周期就是对象从创建开始到最终销毁的整个过程。
为什么要知道Bean的生命周期?
其实生命周期的本质是:在哪个时间节点上调用了哪个类的哪个方法。
我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。
只有我们知道了特殊的时间节点都在哪,到时我们才可以确定代码写道哪。
我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。
Bean生命周期可以粗略的划分为五大步:
第一步:实例化Bean
第二步:Bean属性赋值
第三步:初始化Bean
第四步:使用Bean
第五步:销毁Bean
User
package com.hhb.bean;
public class User {
private String name;
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值");
this.name = name;
}
public User() {
System.out.println("第一步:无参数构造方法执行");
}
//这个方法需要自己写、自己配,方法名随意。
public void initBean() {
System.out.println("第三步:初始化Bean");
}
//这个方法需要自己写,自己配,方法名随意
public void destroyBean() {
System.out.println("第五步:销毁Bean");
}
}
spring.xml
测试
@Test
public void testBeanLifecycleFive(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println("第四步:使用Bean");
//只有正常关闭spring容器才会执行销毁方法
ClassPathXmlApplicationContext context= (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
第一:只有正常关闭spring容器,bean的销毁方法才会被调用。
第二:ClassPathXmlApplicationContext类才有close()方法。
第三:配置文件中的init-method指定初始化方法,destroy-method指定销毁方法。
在以上的5步中,第3步是初始化Bean,如果你还想在初始化前和初始化后添加代码,可以加入”Bean后处理器“。
编写一个类实现BeanPostProcessor类,并且重写before和after方法
package com.hhb.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 BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:执行Bean后处理器的after方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
在spring.xml文件中配置”Bean后处理器“
配置Bean后处理器,这个后处理器将作用于当前配置文件中的所有bean。
Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。
测试以上10步,可以让User类实现5个接⼝,并实现所有⽅法:
BeanNameAware
BeanClassLoaderAware
BeanFactoryAware
InitializingBean
DisposableBean
package com.hhb.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
private String name;
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值");
this.name = name;
}
public User() {
System.out.println("第一步:无参数构造方法执行");
}
//这个方法需要自己写、自己配,方法名随意。
public void initBean() {
System.out.println("第四步:初始化Bean");
}
//这个方法需要自己写,自己配,方法名随意
public void destroyBean() {
System.out.println("第七步:销毁Bean");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("Bean这个类的加载器:"+classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("生产这个Bean的工厂对象是:"+beanFactory);
}
@Override
public void setBeanName(String name) {
System.out.println("这个Bean的名字是:"+name);
}
@Override
public void destroy() throws Exception {
System.out.println("InitializingBean's afterPropertiesSet执行");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("DisposableBean's destroy方法执行");
}
}
Spring根据Bean的作用域来选择管理方式。
对于singleton作用域的Bean,Spring能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁。
而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring容器将不再跟踪其生命周期。
有些时候可能会遇到这样的需求,某个java对象是我们自己new的,然后我们希望这个对象被Spring容器管理,怎么实现?
Student
package com.hhb.bean;
public class Student {
}
Test
@Test
public void testRegisterBean(){
//自己new对象
Student student = new Student();
System.out.println(student);
//将以上自己new的这个对象纳入Spring容器来管理,半路上交给Spring来管理。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("studentBean",student);
//从spring容器中获取
Object studentBean = factory.getBean("studentBean");
System.out.println(studentBean);
}
A对象中有B属性,B对象中有A属性,这就是循环依赖。
Husband
package com.hhb.bean;
public class Husband {
private String name;
private Wife wife;
}
Wife
package com.hhb.bean;
public class Wife {
private String name;
private Husband husband;
}
Husband
package com.hhb.bean;
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
Wife
package com.hhb.bean;
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
spring.xml
singleton表示在整个Spring容器中是单例的,独一无二的。
Test
@Test
public void testCD() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
System.out.println(husbandBean);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(wifeBean);
}
通过测试得知:在singleton+set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。
在singleton+setter模式下,为什么循环依赖不会出现问题,Spring是如何应对的?
主要的原因是:在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”。
第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法)
核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。
只有再scope是singleton的情况下,Bean才会采取提前“曝光”的措施。
spring.xml
执行测试程序:发生了异常如下
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creatingbean with name 'husbandBean': Requested bean is currently in creation: Is there an unresolvablecircular reference?
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.
java:265)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.jav
a:199)
at
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDe
finitionValueResolver.java:325)
... 44 more
翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在⽆法解析的循环引⽤?
通过测试得知,当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。
如果其中一个是singleton,另一个是prototype是没有问题的。
Husband
package com.hhb.bean2;
public class Husband {
private String name;
private Wife wife;
public Husband(String name, Wife wife) {
this.name = name;
this.wife = wife;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
Wife
package com.hhb.bean2;
public class Wife {
private String name;
private Husband husband;
public Wife(String name, Husband husband) {
this.name = name;
this.husband = husband;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
spring.xml
执行结果,出现异常如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creatingbean with name 'hBean': Requested bean is currently in creation: Is there an unresolvable circularreference?
at
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation
(DefaultSingletonBeanRegistry.java:355)
at
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.
java:324)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.jav
a:199)
at
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDe
finitionValueResolver.java:325)
... 56 more
和上一个测试结果相同,都是提示产生了循环依赖,并且Spring是无法解决这种循环依赖的。
主要原因是因为通过构造方法注入导致的,因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分开,必须在一起完成导致的。
Spring为什么可以解决set+singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一时间点上完成的。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢调用setter方法给属性赋值,这就解决了循环依赖的问题。
Spring只能解决setter方法注入的单例Bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean缓存中,则无需创建新的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。