[SPRING] spring-mvc-chat github 애플리케이션 컨텍스트에서 Spring MVC의 DeferredResult 클래스 이해하기
SPRINGspring-mvc-chat github 애플리케이션 컨텍스트에서 Spring MVC의 DeferredResult 클래스 이해하기
다음 봄 mvc 3.2 응용 프로그램 작동 방식을 더 잘 이해하려고합니다. https://github.com/rstoyanchev/spring-mvc-chat
내 질문에 대한 deferredResult 봄 MVC 클래스에 대한 것입니다. 주어진 시간에 chatRequests Map에 채팅 애플리케이션에 연결된 사용자가있는만큼 많은 항목이 있음을 발견했습니다.
채팅 응용 프로그램에 연결된 사용자가 3 명 있다고 가정 해보십시오. 사용자 # 3이 메시지를 게시 할 때 (postMessage 메소드 참조), for 루프 (postMessage 메소드에서)가 세 번 반복되는 것을 볼 수 있습니다. 왜 그런지 알 수 없습니다.
아래 샘플 코드를 포함합니다.
컨트롤러 코드 :
@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);
}
}
}
자바 스크립트 코드 :
$(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());
});
및 html 페이지 :
<!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>
해결법
-
==============================
1.DeferredResult가 무엇을하는지 이해하려면 Servlet 3.0 Async 개념을 이해해야합니다.
DeferredResult가 무엇을하는지 이해하려면 Servlet 3.0 Async 개념을 이해해야합니다.
Servlet 3.0을 사용하면 AsyncContext를 요청에서 가져 와서 일종의 Collection에 저장할 수 있습니다.
AsyncContext aCtx = request.startAsync(request, response);
결과적으로 응용 프로그램 컨테이너 스레드가 해제됩니다.
별도의 스레드에서 일부 작업을 수행하고 결과를 Servlet 응답에 다시 쓰십시오.
aCtx.getResponse().getWriter().print(result);
DeferredResult의 그 시점부터 절대적으로 동일하게 작동합니다.
작은 예 :
이제 5 초마다 타사 서비스에서 견적을 받고 있다고 생각하십시오. 그리고 업데이트 된 내용을 얻기 위해 서버를 모두 폴링하는 클라이언트가 있습니다.
당신은 당신의 컨트롤러 방법을가집니다 :
/** 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; }
이제 컨트롤러 외부의 메소드를 보자.이 메소드는 견적을 받아서 클라이언트에게 응답을 반환한다.
function getQuoteAndUpdateClients(){ String quote = getUpdatedQuoteFromThirdPartyService(); for (DeferredResult<String> deferredResult: someMap){ deferredResult.setResult(quote); } }
-
==============================
2.필자는 Spring의 DeferredResult 클래스 작성자와이 주제에 대해 긴밀히 논의했으며 여기에는 대화의 관련 부분이 있습니다.
필자는 Spring의 DeferredResult 클래스 작성자와이 주제에 대해 긴밀히 논의했으며 여기에는 대화의 관련 부분이 있습니다.
그 kte Rossen Stoyanchev :
-
==============================
3.클라이언트가 연결되면 this.chatRequests에 DeferredResult가 해당 클라이언트에 저장됩니다. 클라이언트가 메시지를 게시하면 모든 DeferredResults (클라이언트 읽기)를 반복하여 결과를 설정합니다. 3 명의 클라이언트가 연결되어있을 때 3 번 발생한다는 것은 논리적 일뿐입니다.
클라이언트가 연결되면 this.chatRequests에 DeferredResult가 해당 클라이언트에 저장됩니다. 클라이언트가 메시지를 게시하면 모든 DeferredResults (클라이언트 읽기)를 반복하여 결과를 설정합니다. 3 명의 클라이언트가 연결되어있을 때 3 번 발생한다는 것은 논리적 일뿐입니다.
from https://stackoverflow.com/questions/15357990/understanding-the-spring-mvcs-deferredresult-class-in-the-context-of-the-spring by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring 3에서 @Aspect와 @Controller를 어떻게 조합 할 수 있습니까? (0) | 2019.03.22 |
---|---|
[SPRING] 스프링 MVC; URL에서 파일 확장을 피 하시겠습니까? (0) | 2019.03.22 |
[SPRING] Spring 4 WebSocket 앱 (0) | 2019.03.22 |
[SPRING] Hibernate 4의 Integrator 패턴과 Spring의 의존성 주입 사용하기 (0) | 2019.03.22 |
[SPRING] Spring 보안 지원되지 않는 구성 속성 (0) | 2019.03.22 |