ChatService
public class ChatService {
private final NetworkService netwrokService; //네트워크 통신담당
private final ObjectMapper objectMapper;//json 객체 변환기
private Long currentUserId;//현재 사용자 id
//이벤트 리스너 (Controller가 등록)
private Consumer<ChatMessageResponse> onMessageReceived;
public ChatService(NetworkService netwrokService) {
this.netwrokService=netwrokService;
this.objectMapper= new ObjectMapper();
this.objectMapper.registerModule(new JavaTimeModule());
}
📌 Consumer<ChatMessageResponse>란?
- Java의 함수형 인터페이스 중 하나입니다.
- Consumer<T>는 입력값을 받아서 처리하지만 반환값은 없는 함수를 의미해요.
- 여기서는 Consumer<ChatMessageResponse> → ChatMessageResponse 객체를 받아서 처리하는 콜백.
- 즉, 메시지가 도착했을 때 실행할 이벤트 리스너를 담는 변수입니다.
예시:
chatService.setOnMessageReceived(msg -> {
System.out.println("새 메시지 도착: " + msg.getContent());
});
- 메시지가 오면 onMessageReceived.accept(msg)가 호출되고,
- 위 람다식이 실행되어 콘솔에 메시지를 출력
// 연결 및 구독 초기화
public void initialize(Long userId, Consumer<ChatMessageResponse> messageListener) {
this.currentUserId = userId;
this.onMessageReceived = messageListener;
String serverUrl = "ws://localhost:8080/ws";
networkService.connect(
serverUrl,
() -> onConnected(null), // 연결 성공 시
this::onError // 에러 발생 시
);
}
외부에서
chatService.initialize(123L, msg -> {
System.out.println("메시지 도착: " + msg.getContent());
});
이렇게 이벤트를 등록하고,
onMessageReceived.accept(msg);
이렇게 실행하면 저장해둔 람다식이 실행된다.
📌 ObjectMapper란?
- Jackson 라이브러리에서 제공하는 JSON 변환기입니다.
- 역할:
- Java 객체 → JSON 문자열 변환
- JSON 문자열 → Java 객체 변환
- new ObjectMapper()로 생성하고, registerModule(new JavaTimeModule())을 추가하면 LocalDateTime 같은 Java 8 날짜/시간 타입도 변환 가능해집니다.
📌 전체 흐름
- NetworkService → STOMP/WebSocket으로 서버와 연결
- 서버에서 메시지 도착 → ChatMessageResponse 객체로 변환
- onMessageReceived.accept(response) 호출 → Controller에서 등록한 콜백 실행
- ObjectMapper는 JSON ↔ 객체 변환을 담당해서 네트워크 메시지를 쉽게 다룸
NetworkService는 인터페이스
=> 즉, ChatService는 "네트워크 통신을 한다"는 사실만 알면 되고, 구체적으로 STOMP인지, 다른 방식인지 몰라도 된다는 게 핵심입니다.
NetworkService networkService = new StompNetworkService(stompClient);
ChatService chatService = new ChatService(networkService);
ChatService는 "네트워크 서비스"만 알면 되니까, 내부에서는 send(), subscribe() 같은 메서드만 호출합니다.
// 입장 처리
private void requestJoin() {
JoinRequest request = new JoinRequest(currentUserId);
networkService.send("/app/chat.join", request);
}
//구독, client 입장 버튼 클릭시 실행 될 코드
public void joinChatRoom() {
// 구독 먼저
networkService.subscribe("/topic/public", this::handlePublicMessage);
networkService.subscribe("/user/queue/whisper", this::handleWhisperMessage);
networkService.subscribe("/user/queue/history", this::handleHistoryMessage);
// 그 다음 입장 요청
requestJoin();
}
// 전체 채팅 전송
public void sendPublicMessage(String content) {
ChatSendRequest request = new ChatSendRequest(currentUserId, content);
networkService.send("/app/chat.send", request);
}
// 귓속말 전송
public void sendWhisper(Long recipientId, String content) {
WhisperRequest request = new WhisperRequest(currentUserId, recipientId, content);
networkService.send("/app/chat.whisper", request);
}
버튼을 누르면 구독 후, 입장 처리가 진행된다.
구독 후, 채팅과 귓속말은 서버의 경로로 보내면 된다.
서버에서 받은 메시지는 JSON이다. 따라서 이를 파싱해서 컨트롤러로 전달해야한다.
📌 1. handlePublicMessage(String json)
// 메시지 핸들러들 (JSON 파싱 후 Controller에 전달)
private void handlePublicMessage(String json) {
try {
ChatMessageResponse msg = objectMapper.readValue(json, ChatMessageResponse.class);
if (onMessageReceived != null) {
onMessageReceived.accept(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
역할: 서버에서 받은 공개 채팅 메시지(JSON 문자열)를 ChatMessageResponse 객체로 변환.
objectMapper.readValue(json, ChatMessageResponse.class) → JSON을 Java 객체로 변환.
변환된 객체(msg)를 onMessageReceived.accept(msg)로 전달 → 컨트롤러나 UI에서 메시지를 처리할 수 있게 함.
📌 2. handleWhisperMessage(String json)
private void handleWhisperMessage(String json) {
// 동일 로직
handlePublicMessage(json);
}
📌 3. handleHistoryMessage(String json)
private void handleHistoryMessage(String json) {
try {
List<ChatMessageResponse> history = objectMapper.readValue(
json,
new TypeReference<List<ChatMessageResponse>>() {}
);
history.forEach(msg -> {
if (onMessageReceived != null) {
onMessageReceived.accept(msg);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
역할: 서버에서 받은 채팅 히스토리(JSON 배열) 처리.
변환된 리스트를 forEach로 돌면서 하나씩 onMessageReceived.accept(msg) 호출 → UI에 과거 메시지들을 순차적으로 전달.
UserService
public class UserStateService {
private final Set<String> activeUsers=new HashSet<>();
private Consumer<Set<String>> onUserListUpdated;
public void setUserListListener(Consumer<Set<String>> listener) {
this.onUserListUpdated = listener;
}
public void updateActiveUsers(Set<String> users) {
this.activeUsers.clear();
this.activeUsers.addAll(users);
if(onUserListUpdated!=null) {
onUserListUpdated.accept(new HashSet<>(activeUsers));
}
}
public Set<String> getActiveUsers() {
return new HashSet<>(activeUsers);
}
public boolean isUserActive(String username) {
return activeUsers.contains(username);
}
}
activeUsers: 현재 접속 중인 사용자들의 이름(ID)을 저장하는 집합(Set). 중복된 사용자 이름 제거를 위해 Set 사용
onUserListUpdated: 이벤트 리스너, 사용자 목록이 갱신될 때 실행할 함수를 저장
📌 메서드 설명
1. setUserListListener(Consumer<Set<String>> listener)
- 외부(UI나 Controller)가 "사용자 목록이 갱신되면 이 코드를 실행해라" 하고 리스너를 등록하는 메서드.
2. updateActiveUsers(Set<String> users)
새로운 사용자 목록을 받아서 activeUsers를 갱신.
- clear() → 기존 목록 비움
- addAll(users) → 새로운 목록 추가
- onUserListUpdated.accept(new HashSet<>(activeUsers)) 실행 → 이벤트 발생.
- 즉, 사용자 목록이 바뀌면 등록된 리스너가 자동으로 호출됨.
'프로젝트' 카테고리의 다른 글
| 자프실 프로젝트 7 - 코드 리팩토링 (0) | 2025.12.06 |
|---|---|
| 자프실 프로젝트 - 6 controller, 이벤트 흐름 (0) | 2025.12.06 |
| 자바 프로젝트-4 클라이언트 시작! stomp서버 연결하기 (0) | 2025.12.05 |
| 자프실 프로젝트-3 (0) | 2025.11.29 |
| 자프실 프로젝트-1 (0) | 2025.11.29 |