程序员人生 网站导航

spring之bean的Scope

栏目:框架设计时间:2015-04-09 09:16:30

上篇文章较为详细了对各种情况下bean依赖的配置做了说明,但1直没有对Scope这个属性进行说明。本篇将1起学习。本文演示代码下载地址

当我们在xml配置文件中配置1个bean的定义的时候,可以认为是配置了1个模板,可以根据这个模板来生成很多个对象来满足全部利用程序的依赖关系,同时我们也能够配置对象的Scope。

Scope可以理解为SpringIOC容器中的对象应当处的限定场景或说该对象的存活空间,即在IOC容器在对象进入相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定以后,容器通常会烧毁这些对象。

截止到目前为止,spring提供了6种类型的scope:

1.               singleton 表示在spring容器中的单例,通过spring容器取得该bean时总是返回唯1的实例

2.               prototype 表示每次取得bean都会生成1个新的对象

3.               request 表示在1次http要求内有效(只适用于web利用)

4.               session 表示在1个用户会话内有效(只适用于web利用)

5.               global Session 与上面类似,但是用于移动装备中的服务器中。

6.               globalSession 表示在全局会话内有效(只适用于web利用)

1般情况下,前两种的scope就已足够满足需求,后几种应用于web容器中,默许的scope是singleton。

注:spring3.0开始提供 SimpleThreadScope ,但是默许没有注册。

单例

基本

Singleton 是spring容器默许采取的scope。注意这里的Singleton和设计模式中所描写的概念不同。设计模式中指每一个classLoader1个类只有1个实例,而这里指每一个Spring容器对1个 beandefinition只有1个实例。

见下图说明:


下节中的代码集中说明Singleton和prototype 

懒加载

默许情况下,Singleton的bean是在spring容器初始化的进程中进行初始化的,这样做的好处是可以尽早的发现配置的毛病。

但是如果有需要的话,可以取消这个机制,使用bean标签的lazy-init 属性 ,以下:


<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>

也能够在配置文件根标签beans 使用以下配置,取消预加载的行动:

<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>

原型

Scope为prototype bean定义,每次需要的时候总是重新创建1个全新的对象,不论是依赖注入的需要,还是调用applicationContext的getBean方法。以下图说明:


与其他类型的scope相比,spring不完全管理scope为prototype的生命周期,spring容器仅仅是实例化、配置和组装1个实例给客户,而没有进1步记录它的状态。Spring容器会履行所有bean的初始化方法,但是对scope为prototype的bean来讲,其destruction生命周期函数不会被履行,所以其持有的昂贵的资源需要客户端自己释放。

下面的代码进1步理解,首先是Client1和client2 两个类,他们完全1样,而AccoutDao是1个空类,甚么也没有:

package com.test.scope.si; /** * @Description:有3个 AccoutDao 的援用,他们的scope顺次为默许、单例(singleton)、原型( prototype) * 省略的get set方法 */ public class Client1 { private AccoutDao accoutDao; private AccoutDao accoutDaob; private AccoutDao accoutDaoc; @Override public String toString() { return "Client1 [accoutDao=" + accoutDao + ", accoutDaob=" + accoutDaob + ", accoutDaoc=" + accoutDaoc + "]"; } }

下面是配置文件:

<?xml version="1.0" encoding="UTF⑻"?> <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"> <!-- 定义3个 AccoutDao bean 顺次为默许配置 单例原型 --> <bean id="account1" class="com.test.scope.si.AccoutDao"></bean> <bean id="account2" class="com.test.scope.si.AccoutDao" scope="singleton"></bean> <bean id="account3" class="com.test.scope.si.AccoutDao" scope="prototype"></bean> <!-- 定义 Client1 bean 和 Client2 bean 他们的定义完全1样,这里只是为了说明 singleton 和 prototype 区分 他们都有3个依赖,分别是以上定义的bean --> <bean id ="c1a" class="com.test.scope.si.Client1"> <property name="accoutDao" ref="account1"></property> <property name="accoutDaob" ref="account2"></property> <property name="accoutDaoc" ref="account3"></property> </bean> <bean id ="c2a" class="com.test.scope.si.Client2"> <property name="accoutDao" ref="account1"></property> <property name="accoutDaob" ref="account2"></property> <property name="accoutDaoc" ref="account3"></property> </bean> </beans>


下面是测试程序:

package com.test.scope; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("allbean.xml"); //测试scope为singleton 和prototype 区分,以下3个bean的定义顺次为默许、singleton 和 prototype 。 //注意 打印的值和后边的 c1a和 c2a的相同和不同的地方 System.out.println(context.getBean("account1")); System.out.println(context.getBean("account2")); System.out.println(context.getBean("account3")); //注意以下的不同的地方,可以看到accoutDaoc 的值总是不1样的。而其他的与上面的打印保持1致。 System.out.println(context.getBean("c1a")); System.out.println(context.getBean("c2a")); } }

测试结果,符合预期:

其他Scope

除以上两种scope,还有request, session和global session3种scope,他们用于web环境中,否则将抛出异常。

其bean的scope和以上类似,不同的是他们的意义。意义见开头的扼要说明,具体的代码例子见下节,下面说下以上3种scope的测试环境。

要测试这3种scope需要使用web项目,web项目部署较为复杂,本文依照spring官方文档和网上的1些资料,把spring和servlet整合,做简单的测试,下面的配置是 web.xml中配置。然后相应的servlet在init方法中从容器中取得依赖:

<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/webAllbean.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener>

Servlet中的init方法中手动注入依赖:

public void init(ServletConfig config) throws ServletException { // TODO Auto-generatedmethod stub super.init(config); WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(this .getServletContext()); c = applicationContext.getBean("c3a", Client3.class); System.out.println("iniiiiiii-------------"); }


注:需要引入aop的相干依赖包。

还有1些其他的方法:

Servlet代理:http://blog.csdn.net/xwl617756974/article/details/7451773

支持autowire的servlet: http://www.tuicool.com/articles/32U3Qr

另外关于Spring的作用域和RequestContextListener作用

不同scope依赖

Spring容器中各个bean中可能存在依赖,而且相互依赖的bean之间的scope可能也不同。不同的scope之间的依赖可能会出现问题。主要是以下两种:

singleton 依赖prototype;

reques、session、application作为依赖。

singleton 依赖prototype

依赖无处不在,当这类情况出现的时候,由于singleton 只实例化1次,所以其所依赖的prototype 的bean也只有1次被被设置依赖的机会。这有时不是我们想要的。

假定 A的scope为singleton,B的scope为prototype ,A依赖B。在调用A的某些方法时,需要全新的B的协作。因而bean定义依赖的方法就会出现问题。

有3种种解决方案:

Spring接口手动注入

在调用相干方法的时候,手动重新注入prototype的bean,这类方法可让bean实现ApplicationContextAware 接口,获得spring容器的援用,从中获得bean。这样做的坏处是和spring的接口耦合在了1起。示例代码以下:

定义了1个全新的类ClientForSp,里面的方法testOne和testTwo都是打印本身,不同的是testOne做了重新注入的处理,有两个AccoutDao类型的prototype依赖用来比较,AccoutDao和以上相同:

package com.test.scope.sid; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import com.test.scope.si.AccoutDao; /** * @Description:测试 singleton依赖prototype ,实现ApplicationContextAware接口,获得bean工厂的援用 * 两个依赖字段均为prototype, * 方法testOne 和 testTwo 均需要依赖字段的协助。 * 方法testOne 每次调用重新获得bean,故每次都是全新的bean。 * 方法testTwo则不是。 * */ public class ClientForSp implements ApplicationContextAware{ private AccoutDao accoutDao; private AccoutDao accoutDao1; private ApplicationContext context; public void testOne(){ this.setAccoutDao(createAccountDao()); System.out.println(this); } public void testTwo(){ System.out.println(this); } public AccoutDao getAccoutDao() { return accoutDao; } public void setAccoutDao(AccoutDao accoutDao) { this.accoutDao = accoutDao; } public AccoutDao getAccoutDao1() { return accoutDao1; } public void setAccoutDao1(AccoutDao accoutDao1) { this.accoutDao1 = accoutDao1; } public AccoutDao createAccountDao(){ return context.getBean("account3", AccoutDao.class); } @Override public String toString() { return "ClientForSp [accoutDao=" + accoutDao + ", accoutDao1=" + accoutDao1 + "]"; } @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { this.context = arg0; } }

配置文件以下:

<!-- 定义 ClientForSp 依赖两个 prototype的bean,但在ClientForSp方法testOne中利用spring接口手动注入--> <bean id ="csp1" class="com.test.scope.sid.ClientForSp"> <property name="accoutDao" ref="account3"></property> <property name="accoutDao1" ref="account3"></property> </bean>


测试代码以下,首先调用两次testTwo方法(没有重新注入依赖),然后调用testOne方法(重新注入了依赖):

ClientForSp sp1 = context.getBean("csp1", ClientForSp.class); sp1.testTwo(); sp1.testTwo(); System.out.println(); sp1.testOne(); sp1.testOne();


测试结果以下,两次调用testTwo方法的打印完全1样,而后调用testOne方法可以看到accoutDao的援用每次都不1样,符合预期:


Lookup methodinjection

spring容器可以通过查找其所管理的已命名的bean作为返回值,来覆盖(override)其所管理的bean的某个方法(查找到的bean作为bean的返回值)。利用这点可以解决以上问题。方法覆盖采取动态生成字节码的情势。被覆盖的方法可以是抽象的,也能够是非抽象的,需要没有任何参数。

方法签名以下:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

解决以上问题,需要使用bean标签的<lookup-method>子标签,示例代码和以上类似,只有配置不同,配置以下:

<!-- 定义 ClientForSp ,依赖两个 prototype的bean,采取 lookup-method方法,在每次调用testOne时,重新注入新的prototype bean --> <bean id ="csp2" class="com.test.scope.sid.ClientForSp"> <lookup-method name="createAccountDao" bean="account3"/> <property name="accoutDao1" ref="account3"></property> </bean>

测试代码和结果不再赘述。

Arbitrary methodreplacement

这是1个不太经常使用的方法。需要额外的1个类实现MethodReplacer接口,故名思议,用来做方法替换的。

另外需要使用bean标签的<lookup-method>子标签<replaced-method>,示例代码和以上类似,注意配置和MethodReplacer的实现:

<!-- 定义 ClientForSp ,依赖两个 prototype的bean,采取 replaced-method方法,强迫替换掉 createAccountDao 方法--> <bean id ="csp3" class="com.test.scope.sid.ClientForSp"> <replaced-method name="createAccountDao" replacer="myReplacer"></replaced-method> <property name="accoutDao" ref="account3"></property> <property name="accoutDao1" ref="account3"></property> </bean> <bean id ="myReplacer" class="com.test.scope.sid.MyAccountCreateor"/>

 

public class MyAccountCreateor implements MethodReplacer { /** * 参数的意思分别是原被调用的方法对象、方法和参数 */ @Override public Object reimplement(Object arg0, Method arg1, Object[] arg2) throws Throwable { System.out.println("replaced"); return new AccoutDao(); } }


测试代码和分析不在赘述。

reques、session、application作为依赖。

当scope为 request, session, globalSession的bean作为依赖注入到其他范围内的bean中时,会产生类似singleton依赖prototype的问题。这类情况下只要使用bean的子标签<aop:scoped-proxy/> 便可。

以下:

<!-- an HTTP request bean exposed as a proxy --> <bean id="account4" class="com.test.scope.si.AccoutDao" scope="request"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a singleton-scoped bean injected with the above beans --> <bean id ="c3a" class="com.test.scope.si.Client3"> <property name="accoutDao" ref="account4"></property> </bean>

虽然代码很简单,但是要理解这个问题。由于c3a的scope为singleton,所以它只被初始化1次,它的依赖accoutDao的scope虽然是request,也只被注入1次,当是不同httpRequest到来是,bean “c3a”的accoutDao依赖总是不变的,这肯定是毛病的。所以加上<aop:scoped-proxy/>子标签,告知spring容器采取aop代理为不同request生成不同的accoutDao对象。1般采取动态生成字节码的技术。如果不使用<aop:scoped-proxy/>标签,则启动时报错。

下面使用具体的代码演示 request、session 的scope 使用Aop代理前后的区分。代码需要web环境,环境的部署见上文。这里配置1个bean具有两个依赖,顺次为requset代理,session代理。

配置以下:

<!-- an HTTP request bean exposed as a proxy --> <bean id="account4" class="com.test.scope.si.AccoutDao" scope="request"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- an HTTP session bean exposed as a proxy --> <bean id="account5" class="com.test.scope.si.AccoutDao" scope="session"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a singleton-scoped bean injected with the above beans --> <bean id ="c3a" class="com.test.scope.si.Client3"> <property name="accoutDao" ref="account4"></property> <property name="accoutDao1" ref="account5"></property> </bean>


类client3试试是1个标准的javabean,两个字段和以上的依赖对应,这里不赘述。测试代码在1个servlet中,以下是它的doget方法:

if (c != null) { System.out.println("request代理每次都不1样"+c.getAccoutDao()); System.out.println("session代理不同会话不1样"+c.getAccoutDao1()); } else { System.out.println("null"); }
把以上代码部署到tomcat中,然后分别用不同的阅读器各访问两次,不同的阅读器来摹拟不同的session,测试结果以下:


以上测试结果是先用电脑阅读器访问两次,在用手机阅读器访问两次。首先,每次访问request依赖的值都不1样。其次,相同阅读器的session依赖1样,不同阅读器不1样。以上符合预期结果。

结束

本篇文章较为详细了介绍了spring bean的scope属性,文中有详细的示例测试代码。从scope到不同的scope之间的依赖关系,特别是在说明有关web的scope的时候,本人花费了较多的时间来部署环境。关于scope还缺少自定义scope的部份,暂且不讨论。期待大家共同进步。本文演示代码下载地址

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐