2013-11-27 43 views
2

我想知道如何確保服務中的某個方法每次只能訪問一次。如何避免java spring中同一會話的控制器方法的併發訪問?

我將通過一個小例子說明:

假設我們有在狀態A(user.state = A)的用戶。這個用戶向我們的java彈簧控制器發送一個HTTP GET請求來獲得一個頁面,例如/ hello。根據他的身份,他將被送到A或B。在此之前,我們將把他的狀態改爲B(見下面的代碼)。

現在,再次假設呼叫dao.doSomething();需要很多時間。如果用戶發送另一個GET(通過刷新其瀏覽器),他將調用完全相同的方法dao.doSomething(),導致2個調用。

你怎麼能避免呢?

如果你發送HTTP 2會發生什麼得到在同一時間?

如何在控制器/服務/模型/數據庫中保持一致?

注1:這裏我們不發出2 HTTP從不同的瀏覽器獲得。我們只是在同一瀏覽器上同時創建它們(我知道最大併發會話解決方案,但這並不能解決我的問題)。

注2:解決方案不應阻止不同用戶對控制器的併發訪問。

我讀過一些關於在服務交易,但我不知道這是解決方案。我也讀過一些關於併發性的內容,但我仍然不明白如何在這裏使用它。

我將不勝感激您的幫助!謝謝!

代碼示例:

@Controller 
public class UserController {  

    @RequestMapping(value='/hello') 
    public String viewHelloPage() { 

     // we get the user from a session attribute 
     if (user.getState() = A) { 
      user.setStatus(B); 
      return "pageA"; 
     } 

     return "pageB"; 
    } 


@Service 
public class UserService { 
    Dao dao; 

    @Override 
    public void setStatus(User user) { 
     dao.doSomething(); 
     user.setStatus(B); 
    } 
} 

回答

0

如果dao.doSomething()做,你只需要發生一次的工作,你應該使用一個冪等方法像PUT或DELETE。沒有任何法律強迫你使用正確的方法,但最糟糕的情況是它是一種自我記錄的方式來告訴全世界你應該如何使用API​​。如果這還不夠,大多數瀏覽器會根據請求的類型嘗試幫助你。例如,瀏覽器通常會使用緩存來避免多個GET。

好像你真正想知道的是如何執行冪等。這是特定於應用程序的。一種通用的方法是在服務器端生成並存儲一個僞唯一標識符,以便客戶端連接到它們的請求。這樣,任何具有相同ID的請求在第一個之後都可以安全地忽略。很明顯,老的ID應該被聰明地驅逐出去。

正如我所說的,解決方案通常是特定於應用程序的。在你上面的例子中,它看起來像你試圖在2個狀態之間切換,而你的實現是服務器端切換。您可以利用客戶端來確保多個請求不會成爲問題。

@RequestMapping(value="/hello", method=RequestMethod.PUT) 
public String test(@RequestParam("state") String state) { 
    dao.setState(user, state) 
    switch (state) { 
     case "A": 
      return "B"; 
     case "B": 
      return "A"; 
     default: 
      return "error"; 
    } 
} 
+0

謝謝,但你沒有回答我的問題... – awesome

+0

看到我的編輯。如果你正在修改狀態,你不應該使用GET。如果你希望它是冪等的,使用PUT。如果不是,則POST。如果你試圖強制冪等性,那麼這是一個單獨的問題。 – Floegipoky

+0

感謝您的更新。我的問題以GET請求爲例,但它也適用於POST。用POST,你最終會遇到同樣的問題.... – awesome

1

雖然我不會推薦它(因爲它基本上阻止來自同一用戶的所有其他呼叫)。在大多數HandlerAdapter實現中,您可以將屬性synchronizeOnSession默認設置爲false,以允許來自同一客戶端的併發請求。當您將此屬性設置爲true時,請求將排隊等待該客戶端。

如何設置取決於您的配置HandlerAdapter

0

如何確保服務中的某個方法每次訪問一次只能訪問一次 。

設法鎖定會話對象在控制器中調用服務方法

0

之前,如果你不介意的配置和使用AOP,那麼下面可能會幫助你

@Aspect 
@Component 
public class NonConcurrentAspect implements HttpSessionListener{ 

    private Map<HttpSession, Map<Method, Object>> mutexes = new ConcurrentHashMap<HttpSession, Map<Method, Object>>(); 

    @Around(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)") 
    public Object handle(ProceedingJoinPoint pjp) throws Throwable { 
     MethodInvocationProceedingJoinPoint methodPjp = (MethodInvocationProceedingJoinPoint) pjp; 
     Method method = ((MethodSignature) methodPjp.getSignature()).getMethod(); 
     ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 
     HttpServletRequest request = requestAttributes.getRequest(); 
     HttpSession session = request.getSession(false); 
     Object mutex = getMutex(session, method); 
     synchronized (mutex) { 
      return pjp.proceed(); 
     } 
    } 

    private Object getMutex(HttpSession session, Method method) { 
     Map<Method, Object> sessionMutexes = mutexes.get(session); 
     Object mutex = new Object(); 
     Object existingMutex = sessionMutexes.putIfAbsent(method, mutex); 
     return existingMutex == null ? mutex : existingMutex; 
    } 

    @Override 
    public void sessionCreated(HttpSessionEvent se) { 
     mutexes.put(se.getSession(), new ConcurrentHashMap<Method, Object>()); 
    } 

    @Override 
    public void sessionDestroyed(HttpSessionEvent se) { 
     mutexes.remove(se.getSession()); 
    } 

} 

它同步在每會話每方法互斥量上。一個限制是,這樣建議的方法不應該互相調用(除非嚴重違反MVC設計模式,否則很難確定),否則您可能會面臨死鎖。

這將處理所有標記爲@RequestMapping的方法,但是如果您只需要幾個方法來防範併發執行,那麼作爲可能的解決方案之一,您可以引入自己的註釋,例如,

@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
public @interface NonConcurrent { 
} 

標籤與此註釋的具體方法,並用自己的上述方面類取代@RequestMapping@Around註解。

在高度爭用的環境中,您可能會想到比內在鎖更先進的解決方案。

但是,我會建議不要使用HandlerAdaptersynchronizeOnSession選項,這不僅因爲它同步同一互斥體上的所有調用,而且不那麼明顯,公共可用互斥體上的同步具有潛在危險。

相關問題