2013-03-12 10 views
7

sto cercando di capire meglio come funziona il seguente Spring MVC 3.2 applicazione: https://github.com/rstoyanchev/spring-mvc-chatCapire classe DeferredResult del Spring MVC nel contesto dell'applicazione primavera-MVC-Chat github

La mia domanda riguarda il deferredResult Spring MVC class. Ho notato che in un dato momento ci sono tante voci nella mappa chatRequests in quanto vi sono utenti connessi all'applicazione di chat.

Dire che ci sono 3 utenti connessi all'applicazione di chat. Vedrai che quando l'utente n. invia un messaggio (vedi metodo postMessage in basso), quindi il ciclo for (nel metodo postMessage) itera tre volte. Non riesco a capire perché sia ​​così.

Ho incluso il seguente codice di esempio.

Codice per il regolatore:

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

codice Javascript:

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

}); 

e html pagina:

<!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> 

risposta

6

Ho discusso questo tema a lungo con l'autore della classe DeferredResult di primavera e qui è la parte rilevante della nostra conversazione:

Per citare Rossen Stoyanchev:

parole povere. Un DeferredResult è associato a una richiesta aperta. Quando la richiesta viene completata, il DeferredResult viene rimosso dalla mappa , e poi, il client invia una nuova richiesta di polling lungo, che aggiunge una nuova istanza DeferredResult

+1

Beh, immagino che se hai discusso di questo con l'autore di "DeferredResult", dovresti posizionare l'intera comunicazione in qualche posto. In modo che tutti possano sapere di più su di esso. – rd22

+1

Penso che [questa serie di diapositive] (http://rstoyanchev.github.io/spring-mvc-32-update/#8) di Rossen Stoyanchev metta in luce anche l'argomento. –

11

Per capire che cosa DeferredResult sta facendo è necessario capire Servlet 3.0 Concetto asincrono

Utilizzando Servlet 3.0 è possibile prelevare AsyncContext dalla richiesta, memorizzarlo in una sorta di raccolta.

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

in seguito al rilascio del thread del contenitore applicazioni.

fare qualche operazione su thread separato e risultato scrivere di nuovo alla risposta Servlet:

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

Da questo punto di vostri DeferredResult opere assolutamente la stessa.

piccolo esempio:

Ora consideriamo che ogni 5 secondi che stai ricevendo una citazione da servizio di terze parti. E ci sono clienti che interrogano a lungo il tuo server ogni volta per essere aggiornati.

hai il tuo metodo di controllo:

/** 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; 
    } 

ora vediamo il metodo al di fuori del controllo che ottiene preventivo e torna risposta al client.

function getQuoteAndUpdateClients(){ 

     String quote = getUpdatedQuoteFromThirdPartyService(); 

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

Ciao Danny. Grazie per la tua risposta. Tuttavia, la mia domanda riguardava piuttosto un punto specifico nel codice fornito, ad esempio, vedere questa parte del mio post: _Sono presenti 3 utenti connessi all'applicazione di chat. Vedrai che quando l'utente # 3 invia un messaggio (vedi il metodo postMessage di seguito), quindi il ciclo for (nel metodo postMessage) itera tre volte. Non riesco a capire perché sia ​​così. – balteo

1

Quando un client si connette un DeferredResult viene memorizzato per quella client in this.chatRequests. Quando un client invia un messaggio, scorre tutti i DeferredResults (leggi i client) per impostare un risultato. È logico che ciò avvenga 3 volte quando ci sono 3 client collegati.

Problemi correlati