2013-03-12 10 views
7

próbuję lepiej zrozumieć, jak działa następnej wiosny MVC 3.2 Zastosowanie: https://github.com/rstoyanchev/spring-mvc-chatZrozumienie wiosną MVC za klasę DeferredResult w kontekście stosowania wiosna-MVC-czatu github

moje pytanie jest o deferredResult Spring MVC class. Zauważyłem, że w danym momencie na mapie chatRequests jest tyle wpisów, ile jest użytkowników połączonych z aplikacją czatu.

Powiedzmy, że jest 3 użytkowników podłączonych do aplikacji do czatu. Zobaczysz, że kiedy użytkownik # 3 wysyła komunikat (patrz metoda postMessage poniżej), to pętla for (w metodzie postMessage) iteruje trzy razy. Nie mogę zrozumieć, dlaczego tak jest.

Załączam przykładowy kod poniżej.

Kod regulatora:

@Controller 
@RequestMapping("/mvc/chat") 
public class ChatController { 

    private final ChatRepository chatRepository; 
    private final Map<DeferredResult<List<String>>, Integer> chatRequests = new ConcurrentHashMap<DeferredResult<List<String>>, Integer>(); 

    @Autowired 
    public ChatController(ChatRepository chatRepository) { 
     this.chatRepository = chatRepository; 
    } 

    @RequestMapping(method = RequestMethod.GET) 
    @ResponseBody 
    public DeferredResult<List<String>> getMessages(@RequestParam int messageIndex) { 

     final DeferredResult<List<String>> deferredResult = new DeferredResult<List<String>>(null, Collections.emptyList()); 
     this.chatRequests.put(deferredResult, messageIndex); 

     deferredResult.onCompletion(new Runnable() { 
      @Override 
      public void run() { 
       chatRequests.remove(deferredResult); 
      } 
     }); 

     List<String> messages = this.chatRepository.getMessages(messageIndex); 
     if (!messages.isEmpty()) { 
      deferredResult.setResult(messages); 
     } 

     return deferredResult; 
    } 

    @RequestMapping(method = RequestMethod.POST) 
    @ResponseBody 
    public void postMessage(@RequestParam String message) { 

     this.chatRepository.addMessage(message); 

     // Update all chat requests as part of the POST request 
     // See Redis branch for a more sophisticated, non-blocking approach 

     for (Entry<DeferredResult<List<String>>, Integer> entry : this.chatRequests.entrySet()) { 
      List<String> messages = this.chatRepository.getMessages(entry.getValue()); 
      entry.getKey().setResult(messages); 
     } 
    } 
} 

JavaScript kod:

$(document).ready(function() { 

    function ChatViewModel() { 

     var that = this; 

     that.userName = ko.observable(''); 
     that.chatContent = ko.observable(''); 
     that.message = ko.observable(''); 
     that.messageIndex = ko.observable(0); 
     that.activePollingXhr = ko.observable(null); 


     var keepPolling = false; 

     that.joinChat = function() { 
      if (that.userName().trim() != '') { 
       keepPolling = true; 
       pollForMessages(); 
      } 
     } 

     function pollForMessages() { 
      if (!keepPolling) { 
       return; 
      } 
      var form = $("#joinChatForm"); 


      that.activePollingXhr($.ajax({url: form.attr("action"), type: "GET", data: form.serialize(), cache: false, 
       success: function(messages) { 
        console.log(messages); 
        for (var i = 0; i < messages.length; i++) { 
         that.chatContent(that.chatContent() + messages[i] + "\n"); 
         that.messageIndex(that.messageIndex() + 1); 
        } 
       }, 
       error: function(xhr) { 
        if (xhr.statusText != "abort" && xhr.status != 503) { 
         resetUI(); 
         console.error("Unable to retrieve chat messages. Chat ended."); 
        } 
       }, 
       complete: pollForMessages 
      })); 
      $('#message').focus(); 
     } 

     that.postMessage = function() { 
      if (that.message().trim() != '') { 
       var form = $("#postMessageForm"); 
       $.ajax({url: form.attr("action"), type: "POST", 
        data: "message=[" + that.userName() + "] " + $("#postMessageForm input[name=message]").val(), 
        error: function(xhr) { 
         console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText); 
        } 
       }); 
       that.message(''); 
      } 
     } 

     that.leaveChat = function() { 
      that.activePollingXhr(null); 
      resetUI(); 
      this.userName(''); 
     } 

     function resetUI() { 
      keepPolling = false; 
      that.activePollingXhr(null); 
      that.message(''); 
      that.messageIndex(0); 
      that.chatContent(''); 
     } 

    } 

    //Activate knockout.js 
    ko.applyBindings(new ChatViewModel()); 

}); 

i html strony:

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> 
<head> 
    <title>Chat</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
</head> 
<body> 
    <h1>Chat</h1> 

    <form id="joinChatForm" th:action="@{/mvc/chat}" data-bind="visible: activePollingXhr() == null"> 
     <p> 
      <label for="user">User: </label> 
      <input id="user" name="user" type="text" data-bind="value: userName"/> 
      <input name="messageIndex" type="hidden" data-bind="value: messageIndex"/> 
      <button id="start" type="submit" data-bind="click: joinChat">Join Chat</button> 
     </p> 
    </form> 

    <form id="leaveChatForm" th:action="@{/mvc/chat}" data-bind="visible: activePollingXhr() != null"> 
     <p> 
      You're chatting as <strong data-bind="text: userName"></strong> 
      <button id="leave" type="submit" data-bind="click: leaveChat">Leave Chat</button> 
     </p> 
    </form> 

    <div data-bind="visible: activePollingXhr() != null"> 
     <textarea rows="15" cols="60" readonly="readonly" data-bind="text: chatContent"></textarea> 
    </div> 

    <form id="postMessageForm" th:action="@{/mvc/chat}" data-bind="visible: activePollingXhr() != null"> 
     <p> 
      <input id="message" name="message" type="text" data-bind="value: message" /> 
      <button id="post" type="submit" data-bind="click: postMessage">Post</button> 
     </p> 
    </form> 
</body> 
<script type="text/javascript" src="../../../resources/js/jquery-1.7.2.min.js" th:src="@{/resources/js/jquery-1.7.2.min.js}"></script> 
<script type="text/javascript" src="../../../resources/js/knockout-2.0.0.js" th:src="@{/resources/js/knockout-2.0.0.js}"></script> 
<script type="text/javascript" src="../../../resources/js/chat.js" th:src="@{/resources/js/chat.js}"></script> 

</html> 

Odpowiedz

6

omówiłem ten temat na długość z autorem klasie DeferredResult sprężyny i tu jest odpowiednia część naszej rozmowy:

Cytując Rossen Stoyanchev:

Z grubsza rzecz biorąc. A DeferredResult jest powiązany z otwartym żądaniem. Jeżeli wniosek zakończeniem, DeferredResult jest usuwany z mapy , a następnie, klient wysyła nowe żądanie długo sondowania, które dodaje nową instancję DeferredResult

+1

Cóż, domyślam się, że jeśli omówiłeś to z autorem 'DeferredResult', powinieneś umieścić całą komunikację w pewnym miejscu. Aby wszyscy dowiedzieli się o tym więcej. – rd22

+1

Myślę, że [ten zbiór slajdów] (http://rstoyanchev.github.io/spring-mvc-32-update/#8) od Rossen Stoyanchev rzuca również nieco światła na ten temat. –

11

Aby zrozumieć co DeferredResult robi trzeba zrozumieć Servlet 3.0 Koncepcja asynchroniczna.

Za pomocą Servlet 3.0 można pobrać AsyncContext z żądania, przechowywać go w rodzaju Collection.

AsyncContext aCtx = request.startAsync(request, response); 

w wyniku tego zostanie opublikowany twój wątek kontenera aplikacji.

Make some operację na oddzielnym wątku i zapisu wyników powrót do odpowiedzi Servlet:

aCtx.getResponse().getWriter().print(result); 

Z tego punktu swoimi DeferredResult prac absolutnie takie same.

Mały przykład:

Teraz uważają, że każde 5 sekund jesteś coraz cytat z usługi strony trzeciej. Masz klientów, którzy długo wyszukują twój serwer w celu uzyskania aktualnych informacji.

masz metodę Kontroler:

/** put deferred result to some HashSet. This is the same logic as you 
     store async context in servlet 3.0, those are clients who are waiting for    
     response 
    **/ 
    @RequestMapping(value="/getQuote.do", method=RequestMethod.GET) 
    @ResponseBody 
    public DeferredResult<String> getQuote(){ 
     final DeferredResult<String> deferredResult = new DeferredResult<String>(); 

     someMap.put(deferredResult); 
     return deferredResult; 
    } 

teraz zobaczmy metody poza kontrolerem który dostaje cytat i zwraca odpowiedź do klienta.

function getQuoteAndUpdateClients(){ 

     String quote = getUpdatedQuoteFromThirdPartyService(); 

     for (DeferredResult<String> deferredResult: someMap){ 
       deferredResult.setResult(quote); 
     } 
} 
+0

Cześć Danny. Dzięki za odpowiedź. Jednak moje pytanie dotyczyło określonego punktu w dostarczonym kodzie, tj. Zobacz tę część mojego postu: "Powiedzcie, że do aplikacji czatu jest 3 użytkowników. Zobaczysz, że gdy użytkownik # 3 opublikuje wiadomość (patrz metoda postMessage poniżej), pętla for (w metodzie postMessage) iteruje trzy razy. Nie mogę się domyślić, dlaczego tak jest. – balteo

1

Gdy klient łączy się, DeferredResult jest przechowywany przez które klient w this.chatRequests. Kiedy klient wysyła wiadomość, przechwytuje ona wszystkie parametry DeferredResults (read clients), aby ustawić wynik. Logiczne jest, że dzieje się to 3 razy, gdy podłączonych jest 3 klientów.

Powiązane problemy