查看原文
其他

Spring 源码解析- Scopes 之 Request 、Session 、Application

ImportNew 2019-10-04

(给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的使用还是需要谨慎的去用,不然非但没有其效果,可能会适得其反。


推荐阅读

(点击标题可跳转阅读)

Spring 中获取 request 的几种方法,及其线程安全性分析

从 Spring Cloud 看一个微服务框架的「 五脏六腑 」

Spring AOP 的实现原理


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

喜欢就点一下「好看」呗~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存