(给ImportNew加星标,提高Java技能) 转自:开源中国,作者:麦克斯 链接:my.oschina.net/wang5v/blog/3017934
Request、Session、Application概念 在这篇Spring源码解析-Singleton Scope(单例)和Prototype Scope(多例)博客中介绍了2个比较常用的scope同时也简单的介绍了本篇博客要讲的这三个不常用的scope的概念,今天来详细揭开这3个很不常用的scope。 这三个只能用于web应用中,即要用于Web的Spring应用上下文(如:XmlWebApplicationContext),如果你用于非web应用中(如ClassPathXmlApplicationContext)是会抛出异常的。 Request Scope 第一个要介绍的就是Request了,顾名思义,如果bean定义了这个scope,标示着这个bean的生命周期就是每个HTTP Request请求级别的,换句话说,在不同的HTTP Request请求中,Request Scope的bean都会根据bean的definition重新实例化并保存到RequestAttribute里面。这也就是说,这个实例只会在一次请求的全过程中有效并可见,当请求结束后,这个bean就会被丢弃,生命周期很短的。又因为每次请求都是独立的,所以你修改Request Scope的bean是只对内部可见,其他的通过相同的bean definition创建的实例是察觉不到的。 怎么去定义Request Scope呢?Spring提供了两种方式: 一种XML方式配置: <bean id='testRequest' class='com.demo.TestRequest' scope='request'/>
另外一种就是注解的方式: @RequestScope @Component public class TestRequest{ // ... }
这样我们就指定了这个Bean的scope为Request的。但是,Request、Session和Application的用法不只是这样就可以了,后面在应用中会更加详细介绍怎么去用这三个东西。 Session Scope 接下来谈下这个Session Scope,会话级别的。scope指定为Session的bean,Spring容器在单个HTTP会话的生命周期中使用bean定义来创建bean的新实例,也就是说,在每次会话中,Session Bean 会实例化,并保存到RequestAttribute里面,跟Request不同的是,每个会话只会实例化一次,而request是每次请求都会实例化一次。当我们,定义了Session的bean,那么标记着这个bean的生命周期就是在一次完整的会话中,所以在特定的HTTP Session 中bean的内部状态修改了,在另外的HTTP Session 实例中根据一样的bean definition创建的实例是感知不到的。当session结束时,这个bean也会随之丢弃掉。 定义Session Scope也有两种方式: 一种XML方式: <bean id='testRequest' class='com.demo.TestRequest' scope='session'/>
另外一种就是注解的方式: @SessionScope @Component public class TestRequest{ // ... }
Application Scope 被Application标记的bean指示了Spring容器通过对整个web应用程序一次性使用bean定义来创建bean的新实例。也就是说,Application Scope bean的作用域在ServletContext级别,并存储为一个常规的ServletContext属性里面。这个跟单例有点类似,但是却是不同的,每个ServletContext里是单例,但是对于Spring的每个ApplicationContext就不一定了。 定义Application Scope也有两种方式: 一种XML方式: <bean id='testRequest' class='com.demo.TestRequest' scope='application'/>
另外一种就是注解的方式: @ApplicationScope @Component public class TestRequest{ // ... }
将短生命周期的bean依赖注入到长生命周期的bean 当我们想要在一个长生命周期的bean如Singleon,注入一个短生命周期的bean如Request的时候,不能像我们定义单例一样去注入实例,众所周知,依赖注入只会发生在bean实例化后,依赖注入后的实例就不会再发生改变,也就是说,bean只会被实例化一次,然后就不会发生改变了,这很明显违背了Request和Session等这些短生命周期的的原理。因为类似Request这种,是要在每次请求中都要去重新实例化一个对象的。如果单纯的使用简单的bean定义,这很明显是不符合的。所以,接下来我们来介绍 <aop:scoped-proxy/>,在这些短周期的bean定义中要加上这个标签,这个标签的作用就是将你这个bean注册成代理实例,并不是真正的实例对象,在依赖注入的时候,注入的是代理的实例,当用到这个代理的时候,才会起获取被代理的对象的实例,这就很好的解决了上面的问题。所以真正要用到Request或者Session的时候是这样定义的: <bean id='testRequest' class='com.demo.TestRequest' scope='session'> <aop:scoped-proxy/> </bean>
加入这个标签后有什么变化呢,在下面的源码剖析里会介绍。其实在实际运用中,要是你在长生命周期的bean中依赖了短生命周期的bean要是没有加上<aop:scoped-proxy/>这个标签就会抛出异常的。注解@RequestScope和@SessionScope以及@ApplicationScope不需要增加什么,默认效果跟xml加上标签<aop:scoped-proxy/>是一样的。 Request、Session、Application简单应用 只有在使用支持web的Spring ApplicationContext实现(如XmlWebApplicationContext)中,才能使用Request、Session、Application作用域。如果将这些作用域与常规的Spring IoC容器一起使用,例如ClassPathXmlApplicationContext,就会抛出一个IllegalStateException异常。所以,我们只能在web应用中才能使用以上三个scope。 为了能够使web应用支持Request、Session、Application级别的作用域,需要在定义bean之前进行一些较小的初始配置,标准作用域,singleton和prototype不需要这个初始设置。如果我们用的是Spring MVC的话则无需设置什么,就可以使用上面3个scope了,这是因为DispatcherServlet 暴露了这三个scope了。但是如果你用的是Strust等web应用的话,就需要配置下,在web.xml里面加入下面这段即可: <filter> <filter-name>requestContextFilter</filter-name> <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>requestContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Request、Session、Application源码剖析 scoped-proxy改变了什么 普通的bean的定义加入这个标签后,spring会将这个bean转成代理工厂的bean定义,具体代码如下: public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName(); BeanDefinition targetDefinition = definition.getBeanDefinition(); String targetBeanName = getTargetBeanName(originalBeanName);
// 为原始bean名称创建一个作用域代理定义, //在内部目标定义中“隐藏”目标bean。 RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName)); proxyDefinition.setOriginatingBeanDefinition(targetDefinition); proxyDefinition.setSource(definition.getSource()); proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add('targetBeanName', targetBeanName); if (proxyTargetClass) { targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); // ScopedProxyFactoryBean's 'proxyTargetClass' default is TRUE, so we don't need to set it explicitly here. } else { proxyDefinition.getPropertyValues().add('proxyTargetClass', Boolean.FALSE); }
// Copy autowire settings from original bean definition. proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition) { proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition); }
// 忽略目标bean,代之以作用域代理。 targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false);
// Register the target bean as separate bean in the factory. registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition // (potentially an inner bean). return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); }
上面代码,可以看出,加上<aop:scoped-proxy/>标签后,spring初始化bean定义的时候会将目标bean转成代理类的定义,而这个代理通过ScopedProxyFactoryBean工厂来创建,所以关键代码在ScopedProxyFactoryBean工厂bean里面。简单来看,当spring实例化这个proxy definition的时候,因为这个proxy definition是个工厂类,所以会去调用工厂的getObject方法,我们看下这个实现: @Override public Object getObject() { if (this.proxy == null) { throw new FactoryBeanNotInitializedException(); } return this.proxy; }
实例化会返回一个proxy的实例,而proxy是在哪里创建的呢,继续看,由于ScopedProxyFactoryBean实现了BeanFactoryAware接口,proxy实例就是在BeanFactoryAware接口的setBeanFactory方法里面实现了,我们看下具体实现: @Override public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ConfigurableBeanFactory)) { throw new IllegalStateException('Not running in a ConfigurableBeanFactory: ' + beanFactory); } ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory(); pf.copyFrom(this); pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName); if (beanType == null) { throw new IllegalStateException('Cannot create scoped proxy for bean '' + this.targetBeanName + '': Target type could not be determined at the time of proxy creation.'); } if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) { pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader())); }
// Add an introduction that implements only the methods on ScopedObject. ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName()); pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped proxy // itself is not subject to auto-proxying! Only its target bean is. pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader()); }
这样新建一个代理,这是通过编程方式的来编写AOP,具体细节不讨论了,我们只要知道这个代理,代理的源是pf.setTargetSource(this.scopedTargetSource);代理执行的时候,都会去获取这个targetSource并执行里面的一个方法getTarget,方法如下: @Override public Object getTarget() throws Exception { return getBeanFactory().getBean(getTargetBeanName()); }
所以可以看出,加入标签<aop:scoped-proxy/>后,在依赖了这个bean的类里面其实依赖注入进来的是他的代理对象,当我们每次用到这个代理的时候,代理就会被拦截并执行getTarget方法来获取被代理对象的实例,就会重新的执行spring实例化bean的操作。 当在单例中依赖注入了Request scope的bean的话,其实依赖注入的是代理的实例并不是真正的实例的,所以每次都会去实例化被代理的对象实例。当然这里,如果我们的在单例里面去依赖注入一个多例的话,如果要每次运行的时候获取的多例每次都不一样的话,我们也可以用这种方法来实现。 实例化Request、Session、Application Bean 实例化Request、Session等就会进入的Spring的实例化过程,在Spring源码解析-Singleton Scope(单例)和Prototype Scope(多例)讲到了单例和多例的创建,其实还有第三个流程就是除了单例和多例外的scopes的实例化流程,部分代码如下: else { //如果不是单例且不是多例则会进入到这个分支 String scopeName = mbd.getScope(); //首先获取支持的sope final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException('No Scope registered for scope name '' + scopeName + '''); } try { //执行实例化并存放到RequestAttribute或者ServletContext属性里面 Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } } }); //跟上篇文章提到的处理工厂对象 bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, 'Scope '' + scopeName + '' is not active for the current thread; consider ' + 'defining a scoped proxy for this bean if you intend to refer to it from a singleton', ex); } }
重点代码是scope的get方法,接下来,来分别看下他们的具体实现: Request Scope的实现: @Override public Object get(String name, ObjectFactory<?> objectFactory) { RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); Object scopedObject = attributes.getAttribute(name, getScope()); if (scopedObject == null) { scopedObject = objectFactory.getObject(); attributes.setAttribute(name, scopedObject, getScope()); } return scopedObject; }
从代码可以看出,首先会去attribute里面获取相对于的scope的对象实例,如果获取到了直接返回,获取不到则从新实例化对象,不管Request还是Session都会执行上面的代码,上面的代码是在抽象类AbstractRequestAttributesScope里面的实现。 接下来看下,Session的具体实现,如下: @Override public Object get(String name, ObjectFactory<?> objectFactory) { Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex(); synchronized (mutex) { return super.get(name, objectFactory); } }
首先,他会先获取当前的session互斥锁,进行同步操作,保证会话创建实例只会创建一次,其他都是从session里面去取,虽然都是RequestAttribute来存放其实内部实现并不是,来看代码: @Override public void setAttribute(String name, Object value, int scope) { //scope 是 request if (scope == SCOPE_REQUEST) { if (!isRequestActive()) { throw new IllegalStateException( 'Cannot set request attribute - request is not active anymore!'); } this.request.setAttribute(name, value); } //scope 是session else { HttpSession session = getSession(true); this.sessionAttributesToUpdate.remove(name); session.setAttribute(name, value); } }
setAttribute里面其实根据scope不同做了不同的处理,Session是放到http session里面,而request则是放http request的attribute里面。所以从这里可以看出,request是针对每次请求都会实例化,在单次请求中是同个实例,当请求结束后,就会被销毁了;而session则是在一次完整的会话只会实例化一次,实例化完后就会缓存在session里面。 总结 至此,Spring的所有的scope基本就解释完了,我们基本上能够知道这些scope如何去用,以及他们各自的原理。我们可以根据业务需求去使用不同的scope,除了单例外的其他scope的使用还是需要谨慎的去用,不然非但没有其效果,可能会适得其反。
|