2015-10-09 47 views
11

Singleton不能自動裝載SessionBean,但是可以使用ScopedProxy。ScopedProxy如何決定使用哪個會話?

假設100個用戶在同一個應用程序中同時有一個有效的Session,那麼ScopedProxy如何決定會話是什麼意思?

我不認爲ScopedProxy選擇任何隨機會話,這在我看來是無稽之談。

  1. ScopedProxy如何決定使用哪個會話?
  2. 如果0個用戶有Session,該怎麼辦?會發生一個NullPointerException
  3. @Async與調用Request-Processing-Thread不同的線程如何將HttpRequest-Context注入到異步任務?
+0

親愛的downvoter,你能解釋一下屬於這個問題的不好嗎?我可能會把它改寫成更合適的形式。 –

回答

9

ThreadLocal幾乎是你正在尋找的答案。

該類提供線程局部變量。這些變量與它們的正常副本不同,它們與 不同,因爲訪問一個 (通過其get或set方法)的每個線程都有其自己的獨立初始化變量 副本。

Spring有RequestContextHolder

Holder類以暴露在線裝 RequestAttributes對象的形式的web請求。如果可繼承標誌 設置爲true,則該請求將由當前線程產生的任何子代 線程繼承。

Inside the class你會看到以下內容:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder = 
      new NamedThreadLocal<RequestAttributes>("Request attributes"); 

這裏是實際的二傳手(注意它是靜態的):

/** 
    * Bind the given RequestAttributes to the current thread. 
    * @param attributes the RequestAttributes to expose, 
    * or {@code null} to reset the thread-bound context 
    * @param inheritable whether to expose the RequestAttributes as inheritable 
    * for child threads (using an {@link InheritableThreadLocal}) 
    */ 
    public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {} 

所以,你可以看到,沒有魔法那裏,只是一個線程特定的變量,由ThreadLocal提供。

如果你足夠這裏古玩是ThreadLocal.get實現(whic在此線程局部變量的當前線程副本返回值):

/** 
* Returns the value in the current thread's copy of this 
* thread-local variable. If the variable has no value for the 
* current thread, it is first initialized to the value returned 
* by an invocation of the {@link #initialValue} method. 
* 
* @return the current thread's value of this thread-local 
*/ 
public T get() { 
    Thread t = Thread.currentThread(); 
    ThreadLocalMap map = getMap(t); 
    if (map != null) { 
     ThreadLocalMap.Entry e = map.getEntry(this); 
     if (e != null) 
      return (T)e.value; 
    } 
    return setInitialValue(); 
} 

正如你可以看到它簡單地依賴於ThreadLocalMap

/** 
* ThreadLocalMap is a customized hash map suitable only for 
* maintaining thread local values. No operations are exported 
* outside of the ThreadLocal class. The class is package private to 
* allow declaration of fields in class Thread. To help deal with 
* very large and long-lived usages, the hash table entries use 
* WeakReferences for keys. However, since reference queues are not 
* used, stale entries are guaranteed to be removed only when 
* the table starts running out of space. 
*/ 
static class ThreadLocalMap { 

getEntry()在Map中執行查找。我希望你現在看到整個畫面。

關於潛在的NullPointerException

基本上,你可以調用代理的方法只有範圍是積極的,這意味着執行的線程應該是一個servlet請求。所以任何異步作業,Commands等都會失敗。

我會說,這是ScopedProxy背後的一個相當大的問題。它確實解決了一些問題,透明地(簡化了調用鏈,爲examaple),但是如果你不遵守規則,你可能會得到java.lang.IllegalStateException: No thread-bound request found

Spring Framework Reference Documentation)說以下內容:

的DispatcherServlet,RequestContextListener和RequestContextFilter全部都是 ,它們完全相同,即將HTTP請求對象綁定到服務該請求的 線程。這使得請求和會話範圍爲 的bean可以進入呼叫鏈的更遠處。

您還可以檢查以下問題:Accessing request scoped beans in a multi-threaded web application

@Async和請求屬性注入

一般來說,有解決問題沒有簡單的方法。如前所示,我們有線程綁定的RequestAttributes。

可能的解決方案是手動傳遞所需的對象,並確保@Async背後的邏輯將此考慮在內。

一個更聰明的解決方案(由Eugene Kuleshov建議)是透明地做到這一點。我將複製代碼以簡化閱讀並將鏈接放在代碼塊下。

import org.springframework.web.context.request.RequestAttributes; 
import org.springframework.web.context.request.RequestContextHolder; 

/** 
* @author Eugene Kuleshov 
*/ 
public abstract class RequestAwareRunnable implements Runnable { 
    private final RequestAttributes requestAttributes; 
    private Thread thread; 

    public RequestAwareRunnable() { 
    this.requestAttributes = RequestContextHolder.getRequestAttributes(); 
    this.thread = Thread.currentThread(); 
    } 

    public void run() { 
    try { 
     RequestContextHolder.setRequestAttributes(requestAttributes); 
     onRun(); 
    } finally { 
     if (Thread.currentThread() != thread) { 
     RequestContextHolder.resetRequestAttributes(); 
     } 
     thread = null; 
    } 
    } 

    protected abstract void onRun(); 
} 

這裏是一個問題:Accessing scoped proxy beans within Threads of

正如你所看到的,這個解決方案依賴於事實的構造將在適當的上下文中執行,因此可以緩存適當的環境,後來它注入。

這裏是另一個非常有趣,主題@Async annotated method hanging on session-scoped bean

+0

針對ThreadLocal示例的+1。我總是嘗試使用threadlocal和http會話示例來描述範圍。 – HRgiger

+0

@PeterRader,看到更新。 –

2

一個單不能自動裝配一個會話Bean,但可以一ScopedProxy。

這種說法有點令人困惑。應改寫爲

春天不能注入會話範圍豆成單作用域bean 除非前者被定義爲(作用域)代理。

換句話說,Spring將無法將非代理會話範圍的bean注入到單例範圍的bean中。它將成功地在單一作用域bean中注入代理會話範圍的bean。

假設100名的用戶有在同一 應用程序的同時有效的會話,請問ScopedProxy決定意味着什麼會議?

澄清的第一件事是,會話是Servlet容器的一個組成部分,由HttpSession表示。 Spring(和Spring MVC)使用會話範圍的bean(以及諸如flash屬性之類的其他東西)將其抽象出來。

HttpSession對象通常通過適當的cookie與用戶相關聯。 HTTP請求提供具有用戶標識值的cookie,並且Servlet容器檢索或創建關聯的HttpSession。換句話說,會話可以從請求中的信息中識別出來。您或Spring需要訪問請求。儘管Spring MVC通常不會將其暴露給處理程序方法(請記住,Spring MVC試圖向您隱藏Servlet API),但Spring MVC顯然可以通過DispatcherServlet訪問請求。

以下或多或少是一個實現細節。 Spring MVC不是將請求對象(HttpServletRequest)一直傳播到調用堆棧,而是將其存儲在RequestContextHolder中。

Holder類以線程綁定的形式公開Web請求 RequestAttributes對象。

它可以做到這一點,因爲Servlet容器一般(即非異步)處理在單一線程的請求。如果您在該請求處理程序線程中執行代碼,則可以訪問該請求。如果您有權訪問該請求,請致電you have access to the HttpSession

實際執行時間很長。如果你想進入它,從SessionScope開始,並努力工作。

這一切都是說Spring並沒有注入具體bean類型的對象,它注入了一個代理。以下示例使用JDK proxies(僅限接口)是會話作用域代理的行爲。鑑於

interface SessionScopedBean {...} 
class SessionScopedBeanImpl implements SessionScopedBean {...} 

春將創建一個代理SessionScopedBean類似(但更復雜),以

SessionScopedBean proxy = (SessionScopedBean) Proxy.newProxyInstance(Sample.class.getClassLoader(), 
     new Class<?>[] { SessionScopedBean.class }, new InvocationHandler() { 
      @Override 
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
       HttpSession session = ...;// get session through RequestContextHolder 
       SessionScopedBean actual = session.getAttribute("some.bean.identifier"); 
       if (actual == null) { 
        // if absent, set it 
        session.setAttribute("some.bean.identifier", actual = new SessionScopedBeanImpl()); 
       } 
       return method.invoke(actual, args); // delegate to actual object 
      } 
     }); 

,並注入該proxy對象插入到單豆(大概控制器)。當你的單例bean即將使用會話範圍的bean時,它實際上是通過代理,代理正在檢索並將調用委託給實際的對象。

如果0個用戶有Session,該怎麼辦?會發生一個NullPointerException

Spring注入代理。代理不是null。這是一個對象。它知道如何檢索和使用實際的目標。使用會話範圍,如果目標不存在,它將被創建並保存在會話中(然後被使用)。


這裏的危險是試圖在會話上下文之外使用會話作用域代理。如前所述,這個技巧是可行的,因爲Servlet容器通過在單個線程中處理單個請求來工作。如果您嘗試在未綁定請求的線程中訪問會話範圍的bean,則會發生異常。

因此,不要嘗試跨線程邊界傳遞會話範圍的bean。 Servlet規範允許您使用Async ProcessingSpring MVC supports itDefferedResultCallable。有一個關於它的博客系列,here。您仍然無法傳遞會話範圍的bean。但是,如果您參考了AsyncContext,則可以自行檢索HttpServletRequest並訪問HttpSession

如果您正在控制如何分配線程(或者更確切地說Runnable),則可以使用一些技術來複制請求上下文like the one described here


以下是有關(會話)範圍和代理一些相關的帖子:

+0

@PeterRader編輯,但請不要開始添加更多和更多的問題。如果您有新問題,請提出一個新問題。 –

2

我會做一個很簡單的解釋

@Component 
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS) 
class YourScopedProxy { 

public String dosomething() { 
     return "Hello"; 
    } 

} 


@Component 
class YourSingleton { 
@Autowired private YourScopedProxy meScopedProxy; 

public String usedosomething(){ 
    return this.meScopedProxy.dosomething(); 
} 
} 


    1. How does the ScopedProxy decide what session to use? 

we have 100 users 

1 user (http session) call YourSingleton.usedosomething => call meScopedProxy : 
=> meScopedProxy is not the YourScopedProxy (original) but a proxy to the YourScopedProxy 
    and this proxy understands the scope 
=> in this case : proxy get real 'YourScopedProxy' object from HTTP Session 


    2. What if 0 users have a Session? Will a NullPointerException occur? 
No because meScopedProxy is a proxy , when u use it 
=> proxy get real 'YourScopedProxy' object from HTTP Session 
相關問題