来自:开源中国,作者:Max
链接:my.oschina.net/wang5v/blog/3017934
请求、会话和应用的概念
在这个Spring源代码分析-Singleton Scope(单例)和Prototype Scope(多例)的博客中,介绍了两个常用的作用域,同时在这个博客中简单介绍了这三个不常用的作用域的概念。今天,我们将详细揭示这三个不常用的范围。这三个只能在web应用中使用,也就是说,要在Web中使用的Spring应用上下文(例如,XmlWebApplicationContext)。如果您在非web应用程序中使用它们(例如,ClassPathXmlApplicationContext),您将引发异常。
请求范围
首先要介绍的是Request。顾名思义,如果bean定义了这个范围,那么它表明这个bean的生命周期是在每个HTTP请求级别。换句话说,在不同的HTTP请求中,请求范围的bean将根据bean定义重新实例化,并保存在RequestAttribute中。也就是说,这个实例只在请求的整个过程中有效和可见。当请求完成时,这个bean将被丢弃,它的生命周期非常短。因为每个请求都是独立的,所以您修改请求范围的bean仅对内部可见,而由同一bean定义创建的其他实例是不可察觉的。如何定义请求范围?Spring提供了两种方式:一种是XML配置;
& ltbean id = " TestRequest " class = " com . demo . TestRequest " scope = " request "/& gt;
另一种是注释的方式:
@RequestScope
@组件
publicclassTestRequest{
// ...
}
这样,我们指定这个Bean的范围是请求的范围。但是,请求、会话和应用程序的使用不限于此。稍后,我们将更详细地介绍如何在应用程序中使用这三样东西。
会话范围
让我们来谈谈这个会话范围,会话级别。作用域被指定为Session的bean,Spring容器使用bean定义在单个HTTP会话的生命周期中创建新的bean实例,也就是说,在每个会话中,Session Bean都会被实例化并保存在RequestAttribute中。与请求不同,每个会话将只实例化一次,而请求将为每个请求实例化一次。当我们定义Session的bean时,它标志着这个bean的生命周期处于一个完整的会话中,所以bean的内部状态在一个特定的HTTP Session中被修改,而在另一个HTTP Session实例中按照相同的bean定义创建的实例是无法察觉的。当会话结束时,bean将被丢弃。还有两种定义会话范围的方法:一种是XML方法:
& ltbean id = " TestRequest " class = " com . demo . TestRequest " scope = " session "/& gt;
另一种是注释的方式:
@SessionScope
@组件
publicclassTestRequest{
// ...
}
适用范围
由应用程序标记的bean表示Spring容器通过为整个网络应用程序使用一次bean定义来创建bean的新实例。也就是说,Application Scope bean的作用域是在ServletContext级别,它被存储为一个常规的ServletContext属性。这个单一的例子是相似的,但不同。每个ServletContext都是一个单独的实例,但是对于Spring的每个ApplicationContext并不一定是这样。还有两种定义应用程序范围的方法:一种是XML方法:
& ltbean id = " TestRequest " class = " com . demo . TestRequest " scope = " application "/& gt;
另一种是注释的方式:
@应用范围
@组件
publicclassTestRequest{
// ...
}
将短期bean依赖注入到长期bean中
当我们想要将一个生命周期短的bean(比如Request)注入到一个生命周期长的bean(比如Singleon)中时,我们不能像定义单个实例那样注入一个实例。众所周知,依赖注入只发生在bean实例化之后,并且依赖注入之后的实例不会再发生变化,也就是说bean只会实例化一次,然后就不会发生变化,这显然违反了Request、Session等生命周期短的原则。因为像请求一样,在每个请求中都需要重新实例化一个对象。如果只是简单的使用一个简单的bean定义,这显然是不一致的。让我们来介绍一下
& ltbean id = " TestRequest " class = " com . demo . TestRequest " scope = " session " & gt;
& ltaop:scoped-proxy/
& lt/bean>。
添加这个标记后发生了什么变化将在下面的源代码分析中介绍。其实在实践中,如果你依赖长命豆中的短命豆,如果你不加
请求、会话和应用的简单应用
请求、会话和应用程序范围只能在支持web的Spring ApplicationContext的实现中使用(如XmlWebApplicationContext)。如果这些作用域与常规的Spring IoC容器一起使用,如ClassPathXmlApplicationContext,将引发IllegalStateException异常。因此,我们只能在web应用中使用上述三种范围。为了使网络应用程序支持请求、会话和应用程序级范围,在定义bean之前,有必要进行一些小的初始配置。标准范围、单例和原型不需要这个初始设置。如果我们使用Spring MVC,我们可以使用上面的三个作用域而不需要设置任何东西,因为DispatcherServlet公开了这三个作用域。但是,如果您正在使用诸如Strust之类的web应用程序,您需要对其进行配置,并将以下段落添加到web.xml中:
& lt过滤器>设置。
& lt过滤器-名称>;requestContextFilter<。/filter-name>。
& ltfilter- class >;org . spring framework . web . filter . RequestContextFilter & lt;/filter- class>。
& lt/filter>。
& lt过滤器-映射>
& lt过滤器-名称>;requestContextFilter<。/filter-name>。
& lturl模式>。/* & lt;/URL-模式>。
& lt/filter-mapping>。
请求、会话和应用程序的源代码分析
作用域代理改变了什么
将普通bean定义添加到这个标记之后,spring会将这个bean变成代理工厂的bean定义。具体代码如下:
publicationstationdefinitionholder createScopedProxy(bean definition onholder定义,
BeanDefinitionRegistry注册表,布尔代理目标类){
string original BanName = definition . GetBeanName();
BeanDefinition TargetDefinition = definition . GetBeanDefinition();
string TargetBeanName = GetTargetBeanName(original BeanName);
//为原始bean名称创建一个范围代理定义,
//在内部目标定义中隐藏目标bean。
rootbandefinition proxyDefinition = new rootbandefinition(ScopedProxyFactoryBean。类);
proxydefinition . setdecoratedefinition(new eandedefinition onholder(target definition,target bean name));
proxydefinition . setoriginatingbedefinition(target definition);
proxyDefinition . SetSource(definition . GetSource());
proxyDefinition . SetRole(TargetDefinition . GetRole());
proxy definition . GetPropertyValues()。添加(" targetBeanName ",TargetBeanName);
if(proxyTargetClass) {
target definition . SetAttribute(AutoProxyTils。PRESERVE _ TARGET _ CLASE _ ATTRIBUTE,布尔值。真);
// ScopedProxyFactoryBean的“proxyTargetClass”默认值为TRUE,因此我们不需要在这里显式设置。
}
else{
proxy definition . GetPropertyValues()。添加(" proxyTargetClass ",布尔值。FALSE);
}
//从原始bean定义中复制autowire设置。
proxyDefinition.setAutowireCandidate考生(targetDefinition.isAutowireCandidate考生());
proxydefinition . set primary(target definition . is primary());
if(TargetDefinition instance of AbstractBeanDefinition){
proxydefinition . copy qualifiers from((抽象定义)目标定义);
}
//忽略目标bean,用作用域代理替换它。
targetDefinition.setAutowireCandidate考生(false);
target definition . set primary(false);
//在工厂中将目标bean注册为单独的bean。
registry . RegisterBeanDefinition(TargetBeanName,TargetDefinition);
//将作用域代理定义作为主bean定义返回
//(可能是内部bean)。
returnNewBeanDefinitionHolder(ProxyDefinition,originalBeanName,definition . Getaliases());
}
从上面的代码可以看出
@覆盖
publicObject getObject(){
if( this.proxy == null) {
throwNewfactorybeannotinitializeexception();
}
returnthis.proxy
}
实例化将返回代理的一个实例,代理是在哪里创建的?继续找,由于ScopedProxyFactoryBean实现了BeanFactoryWare接口,代理实例是在BeanFactoryWare接口的setBeanFactory方法中实现的。我们来看看具体的实现:
@覆盖
public void setBeanFactory(BeanFactory BeanFactory){
if(!(BeanFactory instance of configurationableBeanFactory)){
thrownewIllegalStateException("不在可配置的BeanFactory中运行:"+bean factory);
}
可配置的工厂cbf =(可配置的工厂)工厂;
this . SCOpedTargetSource . SetBeanFactory(BeanFactory);
proxy factory pf = new proxy factory();
pf . copy from(this);
pf . settargetsource(this . scopedtargetsource);
类别<。?>。BeanType = BeanFactory . GetType(this . TargetBeanName);
if(beanType == null) {
thrownewIllegalStateException("无法为bean创建作用域代理' "+ this.targetBeanName +
“”:在创建代理时无法确定目标类型。");
}
if(!isProxyTargetClass()| | BeanType . Isinterface()| | Modifier . isPrivate(BeanType . GetModifiers())){
pf . setinterfaces(Classutils . GetAllinterfacesForClass(BeanType,CBF . GetBeanClassLoader()));
}
//添加只实现ScopedObject上的方法的介绍。
scope object scope object = new defaultscope object(CBF,this . scopedtargetsource . gettargetbeanname());
pf . addadvice(new delegatingintroductioninterceptor(scope object));
//添加AopInfrastructureBean标记,以指示作用域代理
//本身不受自动代理!只有它的目标bean是。
pf . addinterface(aopinfrastructureBean。类);
this . proxy = pf . GetProxy(CBF . GetBeanClassLoader());
}
这样就创建了一个新的代理,就是通过编程来编写AOP。具体细节不讨论。只要我们知道这个代理,代理的来源就是pf . settargetsource(this . scopedtargetsource);当代理执行时,它将获得这个目标源并执行它的一个方法getTarget,如下所示:
@覆盖
publicObject getTarget()引发异常{
returngetBeanFactory()。GetBean(GetTargetBeanName());
}
因此,可以看出,标签
实例化请求、会话、应用程序Bean
实例化请求、会话等。将进入Spring的实例化过程。在Spring源代码分析-Singleton Scope(单个案例)和Prototype Scope(多个案例)中,提到了单个案例和多个案例的创建。其实还有第三个过程,就是范围的实例化过程,除了单个案例和多个异常。一些代码如下:
else{
//如果不是单个案例,也不是多个案例,就进入这个分支
string SCOpe name = mbd . GetScope();
//先获取支持的sope
final Scope Scope = this . scopes . get(ScopeName);
if(scope == null) {
thrownewIllegalStateException("没有为作用域名称' "+作用域名称+" ' "注册的作用域");
}
尝试{
//实例化并放入RequestAttribute或ServletContext属性中
object SCOpedinstance = scope . get(BeanName,newObjectFactory & lt对象>() {
@覆盖
publicObject getObject()引发BeansException {
BeforePrototype creation(BeanName);
尝试{
returncreateBean(beanName,mbd,args);
}
最后{
afterPrototypeCreation(BeanName);
}
}
});
//处理上一篇文章中提到的工厂对象
bean = GetObjectForBeanInstance(ScopeDinstance,name,beanName,mbd);
}
catch(IllegalStateException ex){
throwNewBeacreationException(BeanName,
“作用域“+”对当前线程无效;考虑"+
如果您打算从单例引用它,请为此bean定义一个作用域代理,
ex);
}
}
关键代码是作用域的get方法。接下来,让我们看看它们的具体实现:请求范围的实现:
@覆盖
公共对象获取(字符串名称,对象工厂& lt?>。objectFactory){
RequestAttributes attributes = RequestContextholder . CurrentRequestAttributes();
object scope object = attributes . GetAttribute(name,GetScope());
if(scopedObject == null) {
scope object = objectFactory . GetObject();
attributes.setAttribute(name,scopedObject,GetScope());
}
returnscopedObject
}
从代码中可以看出,首先从属性中获取范围对应的对象实例;如果它被直接返回,那么如果它不能被获取,对象将被重新实例化;请求和会话都将执行上述代码,这些代码是在抽象类抽象请求属性范围中实现的。接下来,看看Session的具体实现,如下所示:
@覆盖
公共对象获取(字符串名称,对象工厂& lt?>。objectFactory){
对象互斥= RequestContextholder . CurrentRequestAttributes()。getsessionMutex();
同步(互斥){
returnsuper.get(名称,ObJectFactory);
}
}
首先他会先获取当前会话互斥,同步操作,保证会话创建实例只创建一次,其他的都从会话中取,虽然都是RequestAttribute存储的,但是内部实现不是。看看代码:
@覆盖
publicvoidsetAttribute(字符串名称,对象值,intscope){
//请求范围
if(scope == SCOPE_REQUEST) {
if(!isRequestActive()) {
thrownewIllegalStateException(
"无法设置请求属性-请求不再活动!");
}
this.request.setAttribute(名称,值);
}
//范围是会话
else{
http session session = GetSession(true);
this . sessionattributestoupdate . remove(name);
session.setAttribute(名称,值);
}
}
SetAttribute实际上是根据不同的作用域进行不同的处理。会话放在http会话中,请求放在http请求的属性中。所以从这里可以看出,请求会针对每个请求进行实例化,在单个请求中是同一个实例,请求完成时会被销毁;会话在一个完整的会话中只实例化一次,实例化后将缓存在会话中。
总结
至此,Spring的所有作用域都已经基本说明了,我们基本可以知道这些作用域的使用方法以及各自的原理。我们可以根据业务需求使用不同的范围,使用单个异常以外的范围需要谨慎使用,否则不会有效果,可能会适得其反。
1.《sessionscope Spring 源码解析- Scopes 之 Request 、Session 、Application》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《sessionscope Spring 源码解析- Scopes 之 Request 、Session 、Application》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/shehui/1051826.html