spring之bean的Scope(windsunmoon)
当我们在xml配置文件中配置一个bean的定义的时候,可以认为是配置了一个模板,可以根据这个模板来生成很多个对象来满足整个应用程序的依赖关系,同时我们也可以配置对象的Scope。
Scope可以理解为SpringIOC容器中的对象应该处的限定场景或者说该对象的存活空间,即在IOC容器在对象进入相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。
截止到目前为止,spring提供了六种类型的scope:
1. singleton 表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例
2. prototype 表示每次获得bean都会生成一个新的对象
3. request 表示在一次http请求内有效(只适用于web应用)
4. session 表示在一个用户会话内有效(只适用于web应用)
5. global Session 与上面类似,但是用于移动设备中的服务器中。
6. globalSession 表示在全局会话内有效(只适用于web应用)
一般情况下,前两种的scope就已经足够满足需求,后几种运用于web容器中,默认的scope是singleton。
注:spring3.0开始提供 SimpleThreadScope ,但是默认没有注册。
单例基本Singleton 是spring容器默认采用的scope。注意这里的Singleton和设计模式中所描述的概念不同。设计模式中指每个classLoader一个类只有一个实例,而这里指每个Spring容器对于一个 beandefinition只有一个实例。
见下图说明:
下节中的代码集中说明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定义,每一次需要的时候总是重新创建一个全新的对象,不论是依赖注入的需要,还是调用applicationContext的getBean方法。如下图说明:
与其他类型的scope相比,spring不完全管理scope为prototype的生命周期,spring容器仅仅是实例化、配置和组装一个实例给客户,而没有进一步记录它的状态。Spring容器会执行所有bean的初始化方法,但是对于scope为prototype的bean来说,其destruction生命周期函数不会被执行,所以其持有的昂贵的资源需要客户端自己释放。
下面的代码进一步理解,首先是Client1和client2 两个类,他们完全一样,而AccoutDao是一个空类,什么也没有:
package com.test.scope.si; /** * @Description:有三个 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-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"> <!-- 定义三个 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 他们的定义完全一样,这里只是为了说明 singleton 和 prototype 区别 他们都有三个依赖,分别是以上定义的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 区别,以下三个bean的定义依次为默认、singleton 和 prototype 。 //注意 打印的值和后边的 c1a和 c2a的相同和不同之处 System.out.println(context.getBean("account1")); System.out.println(context.getBean("account2")); System.out.println(context.getBean("account3")); //注意以下的不同之处,可以看到accoutDaoc 的值总是不一样的。而其他的与上面的打印保持一致。 System.out.println(context.getBean("c1a")); System.out.println(context.getBean("c2a")); } }测试结果,符合预期:
其他Scope除了以上两种scope,还有request, session和global session三种scope,他们用于web环境中,否则将抛出异常。
其bean的scope和以上类似,不同的是他们的意义。意义见开头的简要说明,具体的代码例子见下节,下面说下以上三种scope的测试环境。
要测试这三种scope需要使用web项目,web项目部署较为复杂,本文依照spring官方文档和网上的一些资料,把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的相关依赖包。
还有一些其他的方法:
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 只实例化一次,所以其所依赖的prototype 的bean也只有一次被被设置依赖的机会。这有时不是我们想要的。
假设 A的scope为singleton,B的scope为prototype ,A依赖B。在调用A的某些方法时,需要全新的B的协作。于是bean定义依赖的方法就会出现问题。
有三种种解决方案:
Spring接口手动注入在调用相关方法的时候,手动重新注入prototype的bean,这种方法可以让bean实现ApplicationContextAware 接口,获取spring容器的引用,从中获取bean。这样做的坏处是和spring的接口耦合在了一起。示例代码如下:
定义了一个全新的类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);} @Overridepublic String toString() { return "ClientForSp [accoutDao=" + accoutDao + ", accoutDao1=" + accoutDao1 + "]";} @Overridepublic 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方法的打印完全一样,而后调用testOne方法可以看到accoutDao的引用每次都不一样,符合预期:
Lookup methodinjectionspring容器可以通过查找其所管理的已命名的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这是一个不太常用的方法。需要额外的一个类实现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,所以它只被初始化一次,它的依赖accoutDao的scope尽管是request,也只被注入一次,当是不同httpRequest到来是,bean “c3a”的accoutDao依赖总是不变的,这肯定是错误的。所以加上<aop:scoped-proxy/>子标签,告知spring容器采用aop代理为不同request生成不同的accoutDao对象。一般采用动态生成字节码的技术。如果不使用<aop:scoped-proxy/>标签,则启动时报错。
下面使用具体的代码演示 request、session 的scope 使用Aop代理前后的区别。代码需要web环境,环境的部署见上文。这里配置一个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试试是一个标准的javabean,两个字段和以上的依赖对应,这里不赘述。测试代码在一个servlet中,如下是它的doget方法:
if (c != null) { System.out.println("request代理每次都不一样"+c.getAccoutDao()); System.out.println("session代理不同会话不一样"+c.getAccoutDao1()); } else { System.out.println("null"); }把以上代码部署到tomcat中,然后分别用不同的浏览器各访问两次,不同的浏览器来模拟不同的session,测试结果如下:
以上测试结果是先用电脑浏览器访问两次,在用手机浏览器访问两次。首先,每次访问request依赖的值都不一样。其次,相同浏览器的session依赖一样,不同浏览器不一样。以上符合预期结果。
结束本篇文章较为详细了介绍了spring bean的scope属性,文中有详细的示例测试代码。从scope到不同的scope之间的依赖关系,尤其是在说明有关web的scope的时候,本人花费了较多的时间来部署环境。关于scope还缺少自定义scope的部分,暂且不讨论。期待大家共同进步。本文演示代码下载地址
|
|