歡迎光臨
每天分享高質量文章

Spring 原始碼解析- Scopes 之 Request 、Session 、Application

(給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方式配置:

 

"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方式:

 

"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方式:

 

"testRequest"

class=“com.demo.TestRequest” scope=“application”/>

 

另外一種就是註解的方式:

 

@ApplicationScope
@Component
public class TestRequest{
    // ...
}

 

將短生命週期的bean依賴註入到長生命週期的bean

 

當我們想要在一個長生命週期的bean如Singleon,註入一個短生命週期的bean如Request的時候,不能像我們定義單例一樣去註入實體,眾所周知,依賴註入只會發生在bean實體化後,依賴註入後的實體就不會再發生改變,也就是說,bean只會被實體化一次,然後就不會發生改變了,這很明顯違背了Request和Session等這些短生命週期的的原理。因為類似Request這種,是要在每次請求中都要去重新實體化一個物件的。如果單純的使用簡單的bean定義,這很明顯是不符合的。所以,接下來我們來介紹 ,在這些短週期的bean定義中要加上這個標簽,這個標簽的作用就是將你這個bean註冊成代理實體,並不是真正的實體物件,在依賴註入的時候,註入的是代理的實體,當用到這個代理的時候,才會起獲取被代理的物件的實體,這就很好的解決了上面的問題。所以真正要用到Request或者Session的時候是這樣定義的:

 

"testRequest"

class=“com.demo.TestRequest” scope=“session”>

 

加入這個標簽後有什麼變化呢,在下麵的原始碼剖析裡會介紹。其實在實際運用中,要是你在長生命週期的bean中依賴了短生命週期的bean要是沒有加上這個標簽就會丟擲異常的。註解@RequestScope和@SessionScope以及@ApplicationScope不需要增加什麼,預設效果跟xml加上標簽是一樣的。

 

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裡面加入下麵這段即可:

 


        requestContextFilter
        class>org.springframework.web.filter.RequestContextFilter

class>

requestContextFilter
/*

 

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());
  }

 

上面程式碼,可以看出,加上標簽後,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());
  }

 

所以可以看出,加入標簽後,在依賴了這個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() {
      @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的使用還是需要謹慎的去用,不然非但沒有其效果,可能會適得其反。

    贊(0)

    分享創造快樂