天天看点

Spring-管理bean作用域、生命周期

一、管理bean作用域

在前面,将bean交给了Spring容器管理,在客户端只要调用getBean方法就可以从容器里获取bean实例,大家想想,每调用getBean方法,那么它从容器里获取的bean到底是同一个呢?还是不同?   怎么判断是否是同一个对象呢?可以这样做,代码如下: 

SpringTest.java 

Java代码 

package junit.test;   

import org.junit.BeforeClass;   

import org.junit.Test;   

import org.springframework.context.ApplicationContext;   

import org.springframework.context.support.ClassPathXmlApplicationContext;   

import cn.itcast.service.PersonService;   

public class SpringTest {   

    @BeforeClass  

    public static void setUpBeforeClass() throws Exception {   

    }   

    @Test  

    public void instanceSpring() {   

        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");   

        PersonService personService1 = (PersonService) ctx.getBean("personService");   

        PersonService personService2 = (PersonService) ctx.getBean("personService");   

        System.out.println(personService1 == personService2);   

        // 怎么判断两个对象获取的是否是同一个? 很简单,比较引用是否相同就可以了   

    }   

package junit.test;

import org.junit.BeforeClass;

import org.junit.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.itcast.service.PersonService;

public class SpringTest {

 @BeforeClass

 public static void setUpBeforeClass() throws Exception {

 }

 @Test

 public void instanceSpring() {

  ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

  PersonService personService1 = (PersonService) ctx.getBean("personService");

  PersonService personService2 = (PersonService) ctx.getBean("personService");

  System.out.println(personService1 == personService2);

  // 怎么判断两个对象获取的是否是同一个? 很简单,比较引用是否相同就可以了

 }

}

beans.xml 

Xml代码 

<?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-2.5.xsd">  

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" scope="prototype"></bean>  

</beans> 

<?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-2.5.xsd">

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" scope="prototype"></bean>

</beans>如果返回为true,代表他们引用的对象都是同一个;如果返回的是false,那么得到的就是两个不同的对象。 

运行单元测试代码,输出结果为 true。 说明personService1和personService2这两个变量所引用的对象都是同一个,这就证实了:在默认情况下,bean交给Spring容器管理后,那么这个bean是一个单实例。 

   那么我们能不能改变这种行为呢?假如现在我不想用单实例,我希望每调用一次getBean方法都获取一个新的实例,那该怎么做呢?  这就牵涉到Bean的作用域了

Bean的作用域 

---------------------------------------------------------------- 

.singleton 

在每个Spring IoC容器中一个bean定义只有一个对象实例。默认情况下会在容器启动时初始化bean,但我们可以指定Bean节点的lazy-init=“true”来延迟初始化bean,这时候,只有第一次获取bean会才初始化bean。如: 

<bean id="xxx" class="cn.itcast.OrderServiceBean" lazy-init="true"/> 

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

<beans default-lazy-init="true“ ...>

prototype    每次从容器获取bean都是新的对象。 

request 

session 

global session

.request.session .global session 这三种是web应用才能用的。这里不是web应用,所以不能用。 

上面那个单例模式指定的就是singleton作用范围。  如果没有指定作用域的话,那么默认就是singleton作用域范围。 

当业务需求时每调用一次getBean方法,都要获取一个新的实例的话,这个时候就可以通过prototype这个属性值来指定。这个属性值代表:每调用一下getBean方法,都能从容器里获取到新的实例

SpringTest.java不变。 

beans.xml如下: 

Xml代码 

<?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-2.5.xsd">  

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" scope="prototype"></bean>  

</beans> 

<?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-2.5.xsd">

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" scope="prototype"></bean>

</beans>运行单元测试代码,得出的是false,代表每调用一下getBean方法,它都会返回一个新的对象

二、管理bean生命周期

前面介绍了bean的作用域,这里就引用出了一些问题,大家想想,这bean什么时候进行实例化呢? 

  先改为单实例模式,来看看什么时候实例化,beans.xml如下:

Xml代码 

<?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-2.5.xsd">   

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean">                  

          </bean>   

</beans>  

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean"> </bean> </beans>究竟是调用getBean方法时进行实例化?还是Spring容器起动时就进行实例化呢?下面就进行一下验证,打开PersonServiceBean.java,我们验证什么时候实例化,有个最简单的方法,在它的默认构造函数里面打印出一句话, 

PersonServiceBean.java

Java代码 

package cn.itcast.service.impl;    

import cn.itcast.service.PersonService;    

public class PersonServiceBean implements PersonService {    

    public PersonServiceBean(){    

        System.out.println("我被实例化了。。");    

    }    

    public void save(){    

        System.out.println("我是save()方法");    

    }    

}  

package cn.itcast.service.impl; import cn.itcast.service.PersonService; public class PersonServiceBean implements PersonService { public PersonServiceBean(){ System.out.println("我被实例化了。。"); } public void save(){ System.out.println("我是save()方法"); } }看看在默认的情况下,到底是调用getBean的时候输出这句话?还是在实例化容器的时候? 

SpringTest.java

Java代码 

package junit.test;    

import org.junit.BeforeClass;    

import org.junit.Test;    

import org.springframework.context.support.AbstractApplicationContext;    

import org.springframework.context.support.ClassPathXmlApplicationContext;    

public class SpringTest {    

    @BeforeClass   

    public static void setUpBeforeClass() throws Exception {    

    }    

    @Test public void instanceSpring(){    

        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");    

    }    

}  

package junit.test; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { @BeforeClass public static void setUpBeforeClass() throws Exception { } @Test public void instanceSpring(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); } }执行这个单元测试,从控制台可以看到输出了“我被实例化了”这句话,由此可见,这个实例化对于这种默认情况下(它的作用范围是单例的,而且并没有修改任何的属性),是在容器实例化的时候,就会对bean进行实例化。 

如果bean的作用域范围是scope="prototype"的话,这个bean的实例化到底是在什么时候呢?是在容器实例化之后实例化?还是在调用getBean方法后实例化呢? 

beans.xml

Xml代码 

<?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-2.5.xsd">   

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" scope="prototype">                

          </bean>   

</beans>  

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" scope="prototype"> </bean> </beans>

SpringTest.java

Java代码 

package junit.test;    

import org.junit.BeforeClass;    

import org.junit.Test;    

import org.springframework.context.support.AbstractApplicationContext;    

import org.springframework.context.support.ClassPathXmlApplicationContext;    

import cn.itcast.service.PersonService;    

public class SpringTest {    

    @BeforeClass   

    public static void setUpBeforeClass() throws Exception {    

    }    

    @Test public void instanceSpring(){    

        System.out.println("11111111111");    

        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");    

        System.out.println("22222222222222222");    

        PersonService personService1 = (PersonService)ctx.getBean("personService");    

        System.out.println("333333333333333333333");    

        ctx.close();    

    }    

}  

package junit.test; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.itcast.service.PersonService; public class SpringTest { @BeforeClass public static void setUpBeforeClass() throws Exception { } @Test public void instanceSpring(){ System.out.println("11111111111"); ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); System.out.println("22222222222222222"); PersonService personService1 = (PersonService)ctx.getBean("personService"); System.out.println("333333333333333333333"); ctx.close(); } }输出结果是: 

11111111111 

22222222222222222 

我被实例化了。。 

333333333333333333333 

这说明,当bean的作用域范围是prototpye的时候,它是在我们调用getBean方法时才进行实例化的。那么我们有没有办法去更改这种行为呢?   下面是单实例模式下,更改行为的代码 

beans.xml

Xml代码 

<?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-2.5.xsd">   

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" lazy-init="true">   

          </bean>   

</beans>  

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" lazy-init="true"> </bean> </beans>lazy-init是延迟初始化属性,假如设为false,就是不进行延迟初始化,那么它就在容器实例化的时候对bean进行实例化,如果设成true的话,代表要进行延迟初始化,那么Spring容器实例化的时候不会对bean进行实例化. 

运行单元测试,输出结果为: 

11111111111 

22222222222222222 

我被实例化了。。 

333333333333333333333 

可以看出lazy-init这个属性是起到了作用,我们告诉它不要在Spring容器实例化的时候,对PersonServiceBean这个bean进行实例化,通过lazy-init这个属性就可以指定这种行为。 

如果想为配置文件下所有的bean指定延迟初始化的话,可以在<beans>这个配置节点里面指定属性default-lazy-init="true".当然在实际运用中,是不建议大家使用这个属性的,因为我们希望在应用起动的时候我们看下控制台打印出来的信息,看一些bean是否可以完成它的实例化,如果你在调用getBean的时候才实例化这个bean,那就是说尽能在运行期才能发现错误,所以建议大家少用lazy-init这个属性,除非你要完成一些特别的工作。

现在这个bean的创建时机我们已经知道了,那么在有一些应用场合下,当它在创建bean的时候,我要执行一些资源的打开,也就是说是初始化操作(好比打开数据库连接等等的操作),这时候我们该怎么办呢? 

PersonServiceBean.java如下,

Java代码 

package cn.itcast.service.impl;    

import cn.itcast.service.PersonService;    

public class PersonServiceBean implements PersonService {    

    public void init(){    

        System.out.println("初始化");    

    }    

    public PersonServiceBean(){    

        System.out.println("我被实例化了。。");    

    }    

    public void save(){    

        System.out.println("我是save()方法");    

    }    

    public void destory(){    

        System.out.println("关闭打开的资源");    

    }    

}  

package cn.itcast.service.impl; import cn.itcast.service.PersonService; public class PersonServiceBean implements PersonService { public void init(){ System.out.println("初始化"); } public PersonServiceBean(){ System.out.println("我被实例化了。。"); } public void save(){ System.out.println("我是save()方法"); } public void destory(){ System.out.println("关闭打开的资源"); } }

很明显,这个init方法,如果我们不做任何指定的话,它是不会执行的,Spring容器给我们提供了一个功能,可以指定我们的初始化方法(用init-method这个属性),也就是说当PersonServiceBean这个bean被实例化之后,接下来就会执行init-method指定的方法。 

当然在实际应用中,除了初始化方法对一些资源的打开,我们还需要对资源进行释放,也就是说我们要关闭一些资源,这时候呢,我们需要在PersonServiceBean这个bean被销毁之前先执行destory方法,怎么办呢?我们可以指定destory-method属性. 

大家想想PersonServiceBean这个bean到底什么时候被销毁的呢?如果你没去人为的操作它的话(没有人为的把它删掉),那么默认情况下它是一直在Spring容器中的,也就是说随着Spring容器关闭了,它才会被销毁, 

beans.xml

Xml代码 

<?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-2.5.xsd">   

          <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" lazy-init="false"   

            init-method="init" destory-method="destory">   

          </bean>   

</beans>  

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean" lazy-init="false" init-method="init" destory-method="destory"> </bean> </beans>

SpringTest.java

Java代码 

package junit.test;    

import org.junit.BeforeClass;    

import org.junit.Test;    

import org.springframework.context.ApplicationContext;    

import org.springframework.context.support.AbstractApplicationContext;    

import org.springframework.context.support.ClassPathXmlApplicationContext;    

import cn.itcast.service.PersonService;    

public class SpringTest {    

    @BeforeClass   

    public static void setUpBeforeClass() throws Exception {    

    }    

    @Test public void instanceSpring(){    

        System.out.println("11111111111");    

        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");    

        //用到了AbstractApplicationContext这个抽象类,这个类是被ClassPathXmlApplicationContext所继承的    

        //所以我们可以通过AbstractApplicationContext来引用Spring容器的实例    

        System.out.println("22222222222222222");    

        PersonService personService1 = (PersonService)ctx.getBean("personService");    

        System.out.println("333333333333333333333");    

        ctx.close();//调用Spring容器里面的close方法,对容器进行正常的关闭    

        //以前并没有正常关闭Spring容器,因为我们的应用一执行完,这个Spring容器就被销毁了    

        //以前的执行方法并不是正常的关闭Spring容器,close是正常的关闭容器    

    }    

}  

package junit.test; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.itcast.service.PersonService; public class SpringTest { @BeforeClass public static void setUpBeforeClass() throws Exception { } @Test public void instanceSpring(){ System.out.println("11111111111"); AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); //用到了AbstractApplicationContext这个抽象类,这个类是被ClassPathXmlApplicationContext所继承的 //所以我们可以通过AbstractApplicationContext来引用Spring容器的实例 System.out.println("22222222222222222"); PersonService personService1 = (PersonService)ctx.getBean("personService"); System.out.println("333333333333333333333"); ctx.close();//调用Spring容器里面的close方法,对容器进行正常的关闭 //以前并没有正常关闭Spring容器,因为我们的应用一执行完,这个Spring容器就被销毁了 //以前的执行方法并不是正常的关闭Spring容器,close是正常的关闭容器 } }运行,得到输出结果: 

11111111111 

我被实例化了。。 

初始化 

22222222222222222 

333333333333333333333 

关闭打开的资源 

首先,先执行实例化,实例化之后呢再调用init方法,大家一定要注意它的执行时机(先实例化,再执行init方法).这个方法是容器通过反射技术来调用的。 

destory方法也已经被执行了

指定Bean的初始化方法和销毁方法 

---------------------------------------------------------- 

指定Bean的初始化方法和销毁方法 

<bean id="xxx" class="cn.itcast.OrderServiceBean" init-method="init" destroy-method="close"/>