Spring中的scope及bean生命周期

一、作用域和生命周期

作用域

作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。例如,当希望每次IOC容器返回的Bean是同一个实例时,可以设置scope为singleton;当希望每次IOC容器返回的Bean实例是一个新的实例时,可以设置scope为prototype。scope配置项有5个属性,用于描述不同的作用域:singleton、prototype、request、session、global-session。

生命周期

Spring Bean的生命周期只有四个阶段(实例化 -> 属性赋值 -> 初始化 -> 销毁)。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

主要逻辑都在doCreate()方法中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应。
1️⃣createBeanInstance() -> 实例化
2️⃣populateBean() -> 属性赋值
3️⃣initializeBean() -> 初始化
查看源码能证明实例化、属性赋值和初始化这三个生命周期的存在。至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close()。

二、singleton (单一实例)

  1. 默认情况下,spring在读取xml文件的时候,就会创建对象。
  2. 在创建的对象的时候(先调用构造器),会去调用init-method=".."属性值中所指定的方法。
  3. 对象在被销毁的时候,会调用destroy-method="..."属性值中所指定的方法(例如调用container.destroy()方法的时候)
  4. lazy-init="true",可以让这个对象在第一次被访问的时候创建

此外,singleton类型的bean定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活,典型单例模式,如同servlet在web容器中的生命周期。

三、prototype

  1. spring读取xml文件的时候,不会创建对象。
  2. 在每一次访问这个对象的时候,spring容器都会创建这个对象,并且调用init-method=".."属性值中所指定的方法。
  3. 对象销毁的时候,spring容器不会调用任何方法。因为不是单例,这个类型的对象有很多个,spring容器一旦把这个对象交给请求方之后,就不再管理这个对象了。

最典型的体现就是spring与struts2进行整合时,要把action的scope改为prototype。
因为spring 默认scope 是单例模式,这样只会创建一个Action对象,每次访问都是同一个Action对象,数据不安全。struts2是要求每次次访问都对应不同的Action,scope="prototype" 可以保证当有请求的时候都创建一个Action对象。

如同分苹果,将苹果的bean的scope属性声明为prototype,在每个人领取苹果的时候,我们都是发一个新的苹果给他,发完之后,别人爱怎么吃就怎么吃,爱什么时候吃什么时候吃,但是注意吃完要把苹果核扔到垃圾箱!对于那些不能共享使用的对象类型,应该将其定义的scope设为prototype。

三、request

request、session和global session类型只适用于web程序,通常是和XmlWebApplicationContext共同使用。


Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,该对象的生命周期即告结束,如同java web中request的生命周期。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且它们之间互不干扰,简单来讲,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。

四、session

对于web应用来说,放到session中最普遍的就是用户的登录信息,对于这种放到session中的信息,我们可以使用如下形式的制定scope为session:


Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,比request scope的bean会存活更长的时间,其他的方面没区别,如同java web中session的生命周期

五、global session


global session只有应用在基于porlet的web应用程序中才有意义,它映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。

六、scope配置

1)xml方式

进行bean的配置时,指定scope。

xml配置bean

2)注解方式

前提为配置spring为注解配置。

Spring中的scope及bean生命周期_第1张图片
注解配置bean.png

七、bean的初始化时机

熟悉了Spring容器管理的bean的作用域,接着就要思考一个问题:bean到底是在什么时候进行实例化的?

bean对象无外乎是在以下两个时刻进行实例化的:

  1. 调用getBean()方法时。
  2. Spring容器启动时。

那么bean对象到底是在哪个时刻进行实例化的,这与Bean的作用域有着某种联系。我们以配置Spring管理的bean的作用域的案例为基础进行深入探讨。为了能够清楚地看到bean对象的实例化,修改PersonServiceBean类的代码为:

public class PersonServiceBean implements PersonService {    
public PersonServiceBean() {  
      System.out.println("我被实例化了");   
 }     
@Override    
public void save() {     
     System.out.println("我是save()方法"); 
   }
}
  • 当Spring的配置文件——beans.xml的内容为:
 


 


即bean的作用域为singleton时,修改SpringTest类的代码为:

public class SpringTest {     
   @Test    
    public void test() {        
       // 实例化Spring容器   
       ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    } 
}

此时,测试test()方法,Eclipse控制台输出:

我被实例化了

这说明了当bean的作用域为singleton时,bean对象是在Spring容器启动时就进行创建了。即默认情况下会在容器启动时初始化bean,但也可以指定bean节点的lazy-init=“true”来延迟初始化bean。这时候,只有第一次获取bean会才初始化bean。
如我们将Spring的配置文件——beans.xml的内容改为:

     




lazy-init=”true”指定了不要在Spring容器启动时对这个bean进行实例化。
此时,测试test()方法,Eclipse控制台根本就不会输出这句话:“>我被实例化了”

这时,只有将SpringTest类的代码修改为:

public class SpringTest {     
    @Test   
    public void test() {        
   //实例化Spring容器
   ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
   //从Spring容器取得bean
   PersonService personService = (PersonService) ctx.getBean("personService");
    } 
}

再次测试test()方法,Eclipse控制台才会输出这句话:

我被实例化了

如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init=“true”,如下:

     ...... 


  • 当Spring的配置文件——beans.xml的内容为:
   
  
 


即bean的作用域为prototype时,若SpringTest类的代码为:

public class SpringTest {     
@Test    
public void test() {        
// 实例化Spring容器 
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
   } 
}

测试test()方法,可以发现Eclipse控制台没有输出这句话:“> 我被实例化了”
这就说明了当bean的作用域为prototype时,bean对象并不会在Spring容器启动时就进行创建。
但是若将SpringTest类的代码改为:

public class SpringTest {     
    @Test    
     public void test() {        
     // 实例化Spring容器
     ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
     // 从Spring容器取得bean
    PersonService personService = (PersonService) ctx.getBean("personService"); 
  }
}

此时,再测试test()方法,可以发现Eclipse控制台输出了这句话:

我被实例化了

证实了当bean的作用域为prototype时,bean对象将会在调用getBean()方法时进行创建。

八、指定bean的初始化方法和销毁方法

我们希望在bean被初始化的时候,就初始化某些资源。为了达到这样的目的,我们可修改PersonServiceBean类的代码为:

public class PersonServiceBean implements PersonService {    
   public void init() {       
      System.out.println("初始化某些资源");    
   }    
   public PersonServiceBean() {       
      System.out.println("我被实例化了");    
    }     
    @Override   
     public void save() {        
       System.out.println("我是save()方法");    
    }
}

这样,我们的目的就具体地成为:当Spring容器初始化PersonServiceBean对象之后,就要执行该对象的init()方法。为了达成这样的目的,只须修改Spring的配置文件—beans.xml的内容为:

    
 



若SpringTest类的代码为:

public class SpringTest {     
   @Test    
    public void test() {      
    // 实例化Spring容器     
   ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
   }
 }

测试test()方法,Eclipse控制台将打印:

Spring中的scope及bean生命周期_第2张图片
输出

现在我们又希望在bean被销毁的时候,就释放或关闭某些资源。为了达到这样的目的,我们可修改PersonServiceBean类的代码为:

public class PersonServiceBean implements PersonService {   
    public void init() {        
         System.out.println("初始化某些资源");    
   }      
    public PersonServiceBean() {        
         System.out.println("我被实例化了");    }     
   @Override   
    public void save() {        
        System.out.println("我是save()方法");    
    }      
    public void destroy() {        
        System.out.println("释放初始化的资源");   
    }
}

bean对象到底是什么时候销毁的呢?答案是:如果没有人为地删除它,默认该bean一直在Spring容器中,也就是说随着Spring容器的关闭,该bean才会被销毁。
紧接着,我们要修改Spring的配置文件——beans.xml的内容。

     

 


最后,我们要修改测试类——SpringTest.java的代码为:

public class SpringTest {     
   @Test    
    public void test() {        
     // ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
    // 实例化Spring容器         
    AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");         
     // 正常关闭Spring容器  
      ctx.close();  
   } 
}

此时,测试test()方法,Eclipse控制台将打印:

Spring中的scope及bean生命周期_第3张图片
输出

这就是Spring管理的Bean的生命周期。

你可能感兴趣的:(Spring中的scope及bean生命周期)