๋ณธ ํ๋ก์ ํธ๋ ์คํ๋ง ์น์์ผ ์ฑํ ์๋ฒ ๋ง๋ค๊ธฐ ๋ฅผ ์ค์ตํ๋ฉฐ ๊ณต๋ถํ๊ธฐ ์ํ ํ๋ก์ ํธ ์ ๋๋ค. branch ๋ณ๋ก ์ด 6๋จ๊ณ ๋ชจ๋ ์งํํ ์์ ์ ๋๋ค.
ํด๋น ๋ด์ฉ์ผ๋ก ๋ง๋ ํ๋ก๊ทธ๋จ์ ๋๋ค. ์ฒซ ๋จ๊ณ๋ผ์ firstStep์ด๋ branch์ ์ ์ฅํ์์ต๋๋ค.
https://github.com/dalcheonroadhead/WebSocketBEPractice/tree/firstStep
1. ์ ๊ฐ๋
1-1 Server Logic
ํด๋น ๋ธ๋ก๊ทธ์ Basic WebSocket์ ์ ๊ฐ๋ ์ด๋ค.
์์ฃผ ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์์ง DB์ ์ฐ๊ฒฐํ์ง ์๊ณ , ์๋น์ค์ ์ปจํธ๋กค๋ฌ, ๋ชจ๋ธ, ์น์์ผ ์ค์ ๋๋ ํ ๋ฆฌ๋ง ํ์ํ๋ค.
1-2 Server์ Client ์ ์ฒด Logic
๋จผ์ ๋ด๊ฐ ํท๊ฐ๋ ธ๋ ๋ถ๋ถ์ Rest API์ ์์ผ์ด ๋ค๋ฅธ ์ฅ์์์ mapping ๋๋ค๋ ์ ์ด๋ค. ๋๋ ์น์์ผ ๋ํ ํด๋ผ์ด์ธํธ์ ํต์ ํ๊ธฐ ์ํด์๋ ์ปจํธ๋กค๋ฌ์์ ์ ์๋ฅผ ํ์ฌ์ผ ํ๋ ์ค ์์๋ค. ํ์ง๋ง, ์น ์์ผ์ ๊ฒฝ์ฐ Web Socket Config ์์ ์ฃผ์๋ฅผ ๋ฐ๋ก ํน์ ํ์ฌ mapping ํ๋ ๋ฏ ํ๋ค. ๋ฐ๋ผ์ ํด๋น ์์ ์์๋ ์ฐ๋ฆฌ๊ฐ ์์ ์๋ ์ปจํธ๋กค๋ฌ ๊ฐ์ ๋ ์์ด ๋์ด๋ค.
2. ์ฝ๋ ๋ถ์
2-1 Sever ๋๋ ํ ๋ฆฌ ๊ณ์ธต ๊ตฌ์กฐ
๋ค์์ ๋ด๊ฐ ๋ง๋ ํ๋ก์ ํธ์ ๊ณ์ธต ๊ตฌ์กฐ์ด๊ณ , ํด๋น ์ฌ์ง์ ๋งจ ์์์๋ถํฐ ์ฝ๋ ๋ถ์์ ์์ํ๊ฒ ๋ค.
(1) WebSockChatHandler
์น์์ผ์ด ํด์ผํ ์ผ์ ๋ํ ์ ์๋ฅผ ํ ํด๋์ค์ด๋ค. ์ ๋ณด๋ฉด, ํด๋น Handler๋ก chatService์ ๊ธฐ๋ฅ๋ค์ ์ฌ์ฉํด์ ์ผ์ ์ฒ๋ฆฌํ๊ณ ์๋ค. ํด๋น Handler๋ WebConfig์ ๊ฐ์ฒด๋ก ํฌํจ๋๋ ๋ ์์ด๋ค. WebConfig ๊ฐ ์ปจํธ๋กค๋ฌ ๊ฐ์ ์ฑ๊ฒฉ์ ๊ฐ์ก์์ ํ๋ฒ ๋ ์ธ์งํ๋ค.
@Slf4j // ๋ก๊ทธ ์ฐ๊ธฐ ์ํ ์ฉ๋
@RequiredArgsConstructor // ๋ฐ์์ ์ค๋ช
@Component
public class WebSockChatHandler extends TextWebSocketHandler {
private final ObjectMapper objectMapper;
private final ChatService chatService;
@Override
protected void handleTextMessage (WebSocketSession session, TextMessage message ) throws Exception{
// payLoad๋ ๋ฐ์ดํฐ ์์ฒด๋ฅผ ์๋ฏธํ๋ ๋ง์ด๋ค. ํ๋ฐฐ ๋ฐ์ค๊ฐ ์์ ๋,
// ๋ด๊ฐ ์ํจ ๋ฌผ๊ฑด์ด payLoad์ด๊ณ ๋ฝ๋ฝ์ด, ํ
์ดํ ๋ฑ์ payLoad๊ฐ ์๋๋ค.
String payLoad = message.getPayload();
log.info("payload {}", payLoad);
// 1. ์น์์ผ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฉ์ธ์ง ๋ด์ฉ์ ์ ๋ฌ๋ฐ์ ์ฑํ
๋ฉ์ธ์ง ๊ฐ์ฒด๋ก ๋ณํ
ChatMessage chatMessage = objectMapper.readValue(payLoad, ChatMessage.class);
// 2. ์ ๋ฌ ๋ฐ์ ๋ฉ์ธ์ง์ ๋ด๊ธด ์ฑํ
๋ฐฉ ID๋ก ๋ด ๋ฉ์ธ์ง๋ฅผ ์ ์กํด์ผ ํ๋ ์ฑํ
๋ฐฉ ๊ฐ์ฒด๋ฅผ ๋ถ๋ฌ์จ๋ค.
ChatRoom room = chatService.findRoomById(chatMessage.getRoomId());
// 3. ํด๋น ์ฑํ
๋ฐฉ์ ์
์ฅํด์๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ๋ค(Websocket session)์๊ฒ ํ์
์ ๋ฐ๋ฅธ ๋ฉ์ธ์ง ๋ฐ์ก
room.handleActions(session, chatMessage, chatService);
}
}
โ @RequestArgsConstructor๋?
๊ธฐ์กด์ Spring์์๋ @AutoWired ์ @Component ์กฐํฉ์ ํ์ฉํ์ฌ, ์์กด์ฑ์ ์ธ๋ถ์์ ์ฃผ์ ์ ํ์๋ค. ์์กด์ฑ ์ฃผ์ ์ด๋, ํด๋์ค ์์ ๋ค๋ฅธ ํด๋์ค, ๊ทธ ํด๋์ค ์์ ๋ค๋ฅธ ํด๋์ค๊ฐ ๋ค์ด๊ฐ๋ ํํ์ ๊ฒฐํฉ๋๊ฐ ๋์ ๋ก์ง์ ์ง์ํ๊ณ ์ ๋์จ ๊ฐ๋ ์ผ๋ก, ํด๋น ์์กด์ฑ์ ์ธ๋ถ ๊ด์ ํ์์ ์ฃผ๊ดํด์ ์ฃผ์ ํ๊ฒ ๋ค๋ ๋ป์ด๋ค. ๋ฐ๋ผ์ ํด๋์ค์ ๊ฐ์ฒด๋ค์ ์์ ์ด ๋ฌด์จ ํด๋์ค ์์ ์กด์ฌํ๋์ง ์์ง ๋ชปํ๊ณ , ๋ชจ๋ ์์กด์ฑ ์ฃผ์ ์ Spring์ ์ค์ ํ์ผ์ ์ํด ๊ฒฐ์ ๋๋ค. ์๋๋ root-context์์ ๋ด๊ฐ ์ธ๋ถ์์ ์ฃผ์ ํ๊ธฐ ์ํด ์ฌ์ฉํ ํด๋์ค๋ฅผ ์ผ์ผํ ์ ์ด์ root-context์๊ฒ ์๋ ค์ค์ผ ํ์ง๋ง, ์ด๋ ธํ ์ด์ ์ด ๋์จ ์ด์ ๋ก ์ด๋ฐ ๊ณผ์ ๋ค์ด ๊ฐ์ํ ๋์๋ค.
- @Component๋ผ๊ณ ์ ํ ํด๋์ค๋ Spring์์ ์๋์ผ๋ก ๋น ๊ฐ์ฒด๋ก ๋ฑ๋ก ์ํจ๋ค.
- @AutoWired๋ผ๊ณ ์ ํ ๋ฉค๋ฒ ๋ณ์, ์์ฑ์, setter ๋ฑ์ ๋ฑ๋ก๋ ๋น ๊ฐ์ฒด ์ค์ type์ด ๋ง๋ ๋ ์์ด ์์ผ๋ฉด, ์๋์ผ๋ก ํด๋น ๋ฉค๋ฒ ๋ณ์, ์์ฑ์, setter๋ ๊ทธ ๋น๊ฐ์ฒด๋ก ์ด๊ธฐํ ๋๋ค.์ด๋ฌํ ์์ ์ ํ๋ฒ ๋ ๊ฐ์ํ ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์ด๋ ธํ ์ด์ ์ด ๋ฐ๋ก @RequestArgsConstructor์ด๋ค! ํด๋น ์ด๋ ธํ ์ด์ ์ ํด๋์ค์ ์จ์ฃผ๋ฉด ์์ฑ์ ์ฝ๋๊ฐ ์์ด๋ ํด๋น ํด๋์ค์ ๊ดํด ์์ฑ์ ์ฃผ์ ์ด ์๋์ผ๋ก ์ด๋ฃจ์ด์ง๋ค. ์ด ๋ง์ ๋ชจ๋ ๋ฉค๋ฒ๋ณ์๊ฐ ํด๋น type์ bean ๊ฐ์ฒด๊ฐ ์๋ค๋ฉด ์๋์ผ๋ก ์ฐ๊ฒฐ๋๋ค๋ ์๋ฆฌ์ด๋ค. ์๋ต๋ ์ฝ๋๋ฅผ ๋ค์ ์จ๋ณด์๋ฉด, ์ด๋ ๊ฒ ๋ ๊ฒ์ด๋ค.
- 2๋ฒ์์ ์ ์ ์๋ฏ์ด AutoWired๋ก ๋น ๊ฐ์ฒด์ ์ฐ๊ฒฐํ๋ ค๋ฉด, ๋ฉค๋ฒ ๋ณ์, ์์ฑ์, setter ๋ฑ์ @AutoWried๋ฅผ ์ ์ด์ค์ผ ํ๋ค. (๋ง์ฝ ์์ฑ์๊ฐ ํ๋ ๋ฟ์ด๋ผ๋ฉด, @AutoWired๋ ์๋ตํด๋ ๋๋ค.)
public WebSockChatHandler ( ObjectMapper objectMapper, ChatService chatService){
this.objectMapper = objectMapper;
this.chatService = chatService;
}
โก ObjectMapper๋?
JSON ํ์์ ๊ฐ์ฒด๋ฅผ ์ง๋ ฌํ ํ๊ฑฐ๋ ์ญ์ง๋ ฌํ ํ ๋, ์ฌ์ฉํ๋ ๊ธฐ์ ์ด๋ค. ๋จผ์ ์์ ๋์จ JSON๊ณผ ์ง๋ ฌํ, ์ญ์ง๋ ฌํ์ ๋ํด ์์๋ณด์.
โก - โ ์ฉ์ด์ ๋ฆฌ
JSON: JavaSript Object Notation์ ์ฝ์๋ก, "ํค : ๊ฐ" ์์ผ๋ก ์ด๋ฃจ์ด์ง, ๋ฐ์ดํฐ ์ ๋ฌ์ฉ Textํ ๊ฐ์ฒด ํฌ๋งท์ด๋ค.
์ง๋ ฌํ: ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ฑฐ๋ ์ ์ฅํ ๋, ๋ฐ์ดํฐ์ ํํ๋ ๋ฐ์ดํธ ํ ๋ฌธ์์ด์ด์ด์ผ ํด์, Object -> String ๋ฌธ์์ด๋ก ๋ฐ๊พธ๋ ์์
์ญ์ง๋ ฌํ: ์์ ๋ฐ์ ๋ฐ์ดํฐ๋ String ๋ฌธ์์ด ํํ์ผํ
๋ฐ, ์ด๊ฑธ ๋ค์ ์ธ ์ ์๋ ๊ฐ์ฒด๋ก ๋ฐ๊พธ๋ ์์
์ด๋ค. (String -> Object)
objectMapper๋ ๋ง ๊ทธ๋๋ก ๊ฐ์ฒด๋ฅผ String์ผ๋ก ํน์ String์ ๊ฐ์ฒด๋ก mapping ํด์ฃผ๋ ๋
์์ด๋ผ๋ ์๋ฏธ์ด๋ค.
๊ทธ๋ผ ์ปจํธ๋กค๋ฌ๋ ํญ์ ๊ฐ์ฒด๋ฅผ String์ผ๋ก ๋ฐ์ ํ
๋ฐ ์ด๋ป๊ฒ ์ฐ๋ฆฌ๋ ObjectMapper ์์ด๋ ๊ฐ์ฒด ํํ๋ก ๋ฐ๋ก ์ธ ์ ์์๊น? SpringBoot์ ๊ฒฝ์ฐ, spring-boot-starter-web์ด๋ผ๋ ์์กด์ฑ์ ์์
ํด์ ์ฌ์ฉํ๊ณ ์๋๋ฐ, ์ด ์์ JSON ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํฌํจ๋์ด ์์ด์ ๊ธฐ๋ณธ์ ์ผ๋ก Object <-> String์ ์๋์ผ๋ก ๋ณํํด์ค๋ค. (JSON ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ JAVA์์ JSON์ ๊ณ ์์ค์ผ๋ก ์ฒ๋ฆฌํด์ฃผ๋ ์ฒ๋ฆฌ๊ธฐ ์ด๋ค.) ํ์ง๋ง ํ์ฌ ์ฐ๋ฆฌ๊ฐ ์ฐ๋ ์์ผ์ ๊ฒฝ์ฐ, SpringBoot๊ฐ ์ปจํธ๋กค๋ฌ ์ฒ๋ผ ์๋์ผ๋ก ๋ณํ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ง ์๊ธฐ ๋๋ฌธ์ ObjectMapper๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๋ฆฌ๊ฐ ์ง์ ์ฒ๋ฆฌ ํด์ผํ๋ค. (์์์ ๋ดค๋ฏ์ด ์์ผ์ ์ปจํธ๋กค๋ฌ์ ๋ค๋ฅธ ์๋ก์ด ์ฐฝ๊ตฌ๋ฅผ ์ฌ๋ ๋
์์ด์๋ค.)
์ด์ objectMapper๋ฅผ ์ด๋ป๊ฒ ์จ์ผํ๋์ง ์์๋ณด์.
โก - โ ์ฌ์ฉ๋ฒ
๋จผ์ ์ฌ์ ์ค์ ์ด ์์ด์ผ ํ๋ค. ObjectMapper๋ String ๋ฌธ์์ด์ ๋ค์ ๊ฐ์ฒด๋ก ๋ฐ๊ฟ ์ ์๋ ์ค๋ช
์๊ฐ ์์ด์ผ ํ ๊ฒ์ด๋ค. (์ค๋ช
์ ์์ด ์กฐ๋ฆฝ์ ์ด๋ป๊ฒ ํ๊ฒ ๋๊ฐ!) ์ฌ๊ธฐ์ ์ค๋ช
์๋ ์ฐ๋ฆฌ๊ฐ ์ญ ์ง๋ ฌํ๋ฅผ ํตํด ๋ง๋๋ ค๋ ๊ฐ์ฒด์ ํด๋์ค์ด๋ค. ์ด๋ ํด๋์ค ์์ ํ์๋ก ์กด์ฌํด์ผํ๋ ๊ตฌ์ฑ์์๋ ์์ฑ์์ Getter์ด๋ค.
ObjectMapper๋ ํด๋์ค์ ์์ฑ์๋ฅผ ํตํด์ ์ง๋ ฌํ๋ ๋ฌธ์์ด์ ๊ฐ์ฒด๋ก ๋ฐ๊พธ๊ณ , Getter๋ฅผ ํตํด์ ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๋ฐ๊พผ๋ค!
๋ค์์ ์์์ด๋ค.
@Getter // Object -> String ๋ฌธ์์ด๋ก ๋ฐ๊ฟ ๋ ํ์! --> ์ฌ๊ธฐ์๋ lombok์ผ๋ก ๋ฐ๋ก ์ฒ๋ฆฌํ๋ค.
class Car {
private String name;
private String color;
// String ๋ฌธ์์ด => Object๋ก ๋ฐ๊ฟ ๋ ํ์!
public Car(String name, String color) {
this.name = name;
this.color = color;
}
public Car() {
this.name = null;
this.color = null;
}
}
์ง๋ ฌํ ์ : mapper.writeValueAsString(car)
์์ ์กด์ฌํ๋ Car๋ผ๋ ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๋ฐ๊พธ๋ ์์ ์ด๋ค.
ObjectMapper mapper = new ObjectMapper();
Car car = new Car("K5", "gray");
String text = mapper.WriteValueAsString(car); //{"name":"K5","color":"gray"}
*์ญ์ง๋ ฌํ ์: mapper.readValue("๋ฌธ์์ด", ํด๋์ค์ด๋ฆ.class) *
class.class๋ ํด๋น ํด๋์ค์ ๋ฉํ์ ๋ณด๊ฐ ๋ด๊ธด Reflection API์ด๋ค. ์ฐ๋ฆฌ๋ ์ด ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ ๋ฌธ์์ด์ ๋ค์ ๊ฐ์ฒด๋ก ๋ณํํ๋ค.
Car carObject = mapper.readValue(text, Car.class); //Car{name='k5',color='gary'}
์ฃผ์์ !
์์์ ObjectMapper๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํด๋์ค์ ๊ผญ ๋ค์ด๊ฐ์ผ ํ๋ ํ์ ์์๊ฐ ์์๋ค. ์ด๋ getter ์ด์ธ์ get์ด๋ผ๋ ์ด๋ฆ์ผ๋ก ์์ํ๋ ๋งค์๋๊ฐ ์์ด์ผ ํ๋ค! ์๋ํ๋ฉด, objectMapper๋ get์ผ๋ก ์์ํ๋ ๋งค์๋๋ฅผ getter๋ผ๊ณ ์ธ์ํ๊ณ , ๊ทธ get ๋ค์ ์ด๋ฃจ์ด์ง ์ด๋ฆ๊ณผ ๊ฐ์ ์ด๋ฆ์ ๋ฌธ์์ด ๋ด ๋ฌธ์๋ฅผ mapping ํด๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ด๋ค.
(2) WebSockConfig
์๊น ์ต์ด๋ก ๋ง๋ handler๋ ์น ์์ผ์ด ์ด๋ป๊ฒ ์ผ ํด์ผํ๋์ง ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ์๋ ค์ฃผ๋ java class ํ์ผ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. ์ด๋ฒ์ ์์๋ณผ WebSockConfig๋ webSocket ์ ์ฒด์ ๋ํ ์ค์ ํ์ผ์ด๋ค. ์ฌ๊ธฐ์๋
- handler ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์น์์ผ์ ๋ฑ๋กํ๋ค. ์ด์ ์น์์ผ์ ๋ฑ๋ก๋ handler๋ฅผ ์ด์ฉํด ํด๋ผ์ด์ธํธ์ ์๊ตฌ์ ๋ฐ๋ฅธ ์ผ ์ฒ๋ฆฌ๋ฅผ ์งํํ๋ค.
- ํด๋ผ์ด์ธํธ๊ฐ ์น ์์ผ์ ์ฌ์ฉํ๊ธฐ ์ํด์ ์ด๋๋ก ์ ์ํด์ผ ํ๋์ง ์ฃผ์๋ฅผ ์๋ ค์ค๋ค. (์ด๋ ๋ณด์ ์ฒ๋ฆฌ๋ฅผ ๋ง๊ธฐ์ํด AllowedOrigin ์ค์ ์ ํด์ผํ๋ค.)
// BasicHandler๋ฅผ ์ด์ฉํ์ฌ WebSocket์ ํ์ฑํ ํ๊ธฐ ์ํ Config ํ์ผ
@RequiredArgsConstructor
@Configuration
@EnableWebSocket // ์น ์์ผ ํ์ฑํ ํ๋ ์ด๋
ธํ
์ด์
public class WebSockConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
// ์น์์ผ์ Handler๋ฅผ ๋ฑ๋กํ๋ ๋งค์๋
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// ์น ์์ผ์ ์ ์ํ๊ธฐ ์ํ endpoint๋ฅผ "ws/chat"์ผ๋ก ์ค์
// ๋ค๋ฅธ ์๋ฒ์์๋ ์ ์ ๊ฐ๋ฅํ๋๋ก CORS : setAllowedOrigins("*")์ ์ค์
registry.addHandler(webSocketHandler, "ws/chat").setAllowedOrigins("*");
}
}
โ @Configuration ์ด๋?
@Configuration์ด๋ ์ด๋
ธํ
์ด์
์ด ๋ฌ๋ฆฐ ํด๋์ค ๋ํ Bean Factory์ ์๋์ผ๋ก ๋ฑ๋ก ๋๋ค. @Configuration์ด๋ ์ด๋
ธํ
์ด์
์์๋ @Conponent๋ผ๋ ํ๊ทธ๊ฐ ํฌํจ๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ฅ @Component์ ๋ค๋ฅธ ์ ์, @Configuration ์ด๋
ธํ
์ด์
์ ๊ฒฝ์ฐ ๊ทธ ์์ ๋ค์ด์๋ Bean ๊ฐ์ฒด๋ค๋ ์ฑ๊ธํค์ผ๋ก ์ฌ์ฉ๋๋๋ก ๋ณด์ฅํ๋ค. (ํ๋์ ํด๋์ค ๋น ํ๋์ ๊ฐ์ฒด๋ง ์กด์ฌํ๊ณ , ํ์ํ ๋๋ง๋ค ๊ทธ๊ฒ์ ์ฌํ์ฉ ํ๋๋ก)
์๋ฅผ ๋ค์ด ์ค๋ช
ํด๋ณด๊ฒ ๋ค.
์๋ฅผ ๋ค์ด ์ด๋ฐ ์์ผ๋ก ํด๋์ค๋ค์ด ๋ง๋ค์ด์ ธ ์๊ณ ์ฐ๊ด๊ด๊ณ๊ฐ ์๋ค๊ณ ํด๋ณด์.
AppConfig์ ๊ฒฝ์ฐ Mybean ๊ฐ์ฒด์ MyBeanConsumer ๊ฐ์ฒด๋ฅผ ๋ ๋ค ์ฌ์ฉํ๊ณ , MyBeanConsumer ๊ฐ์ฒด๋ Mybean ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค. ๋ง์ฝ AppConfig๊ฐ @Configuration ํ๊ทธ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด, ํด๋น ํด๋์ค๊ฐ ๋ฉค๋ฒ ๊ฐ์ฒด๋ก ์ฌ์ฉํ๊ณ ์๋ ๋ฉค๋ฒ๋ณ์๋ค์ ๋ฌด์กฐ๊ฑด ์ฑ๊ธํค์ ์ง์ผ์ผ ํ๋ค. ๋ฐ๋ผ์ AppConfig๋ฅผ ์คํํ ๊ฒฝ์ฐ ์ถ๋ ฅ๋ ๋ฌธ๊ตฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
Bean ๊ฐ์ฒด๊ฐ ์์ฑ๋์์ต๋๋ค.
BeanConsumer ๊ฐ์ฒด๊ฐ ์์ฑ๋์์ต๋๋ค.
์์ฑํ Bean ๊ฐ์ฒด์ ํด์ฌ์ฝ๋๋ 164777052 ์
๋๋ค.
์์ ์์์์๋ @Configuration์ด ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์, MyBeanConsumer์์๋ Bean ๊ฐ์ฒด๋ฅผ ํ๋ ๋ ๋ง๋ค์ด์ ์ฌ์ฉํ ์๋ ์์์ง๋ง, ์ด๋ฏธ AppConfig ๊ฐ์ฒด๋ฅผ ๋ง๋ค ๋, ๋ง๋ค์๋ MyBean ๊ฐ์ฒด๋ฅผ ๋ค์ ์ฌํ์ฉํ๋ค. ๋ฐ๋ผ์ MyBean, MyBeanConsumer ๊ฐ์ฒด๊ฐ ํ๋์ฉ๋ง ์กด์ฌํ๋ค. ๋ง์ฝ AppConfig์ @Configuration ์ด๋ ธํ ์ด์ ์ ์ ๊ฑฐํ๊ณ ๊ทธ๋ฅ @Component๋ฅผ ์ฌ์ฉํ๋ฉด,
Bean ๊ฐ์ฒด๊ฐ ์์ฑ๋์์ต๋๋ค.
Bean ๊ฐ์ฒด๊ฐ ์์ฑ๋์์ต๋๋ค.
BeanConsumer ๊ฐ์ฒด๊ฐ ์์ฑ๋์์ต๋๋ค.
์์ฑํ Bean ๊ฐ์ฒด์ ํด์ฌ์ฝ๋๋ 294238503 ์
๋๋ค.
์ด์ AppConfig์ ๋ฉค๋ฒ ๋ณ์๋ค์ ๋ํ ์ฑ๊ธํค ์์น์ ์งํฌ ํ์๊ฐ ์์ด์ก๋ค. AppConfig๋ฅผ ํ๋ฉด์ MyBean ๊ฐ์ฒด์ MyBeanConsumer ๊ฐ์ฒด๊ฐ ๋ง๋ค์ด์ก์ง๋ง, MyBean ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด์ MyBean ๊ฐ์ฒด๊ฐ ํ๋ฒ ๋ ์์ฑ๋๋ค. ๋ฐ๋ผ์ ๋์งธ ์ค์ MyBean ๊ฐ์ฒด๋ MyBeanConsumer ๊ฐ์ฒด ์์ MyBean ๊ฐ์ฒด์ด๋ค. 4๋ฒ์งธ ์ค์ MyBean ๊ฐ์ฒด์ ํด์ฌ์ฝ๋๋ 2๋ฒ์งธ ์ค์ ์์ฑ๋ MyBeanConsumer ๊ฐ์ฒด ์์ MyBean์ ํด์ฌ์ฝ๋์ด๋ค.
โก WebSocketHandlerRegistry ๋ถ์
// ์น์์ผ์ Handler๋ฅผ ๋ฑ๋กํ๋ ๋งค์๋
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "ws/chat").setAllowedOrigins("*");
}
๋ฉค๋ฒ ๋ณ์๋ก ํ์ฑ๋ Handler ๊ฐ์ฒด๋ฅผ ์น์์ผ ์์ฒด์ ์ฅ์ฐฉํ๋ ๋งค์๋์ด๋ค. addHandler๋ฅผ ์ด์ฉํ์ฌ, ์ฅ์ฐฉํ Handler์ , ํด๋น handler๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ์ฃผ์๋ฅผ ๋ช ์ํด์ฃผ๋ฉด ๋๋ค. ์ฌ๊ธฐ์๋ CORS ์ ์ฑ ์ ๋ฐ ๋ฌถ์ด์ง ์๊ณ , ์ด๋ ๋ธ๋ผ์ฐ์ ์์๋ ์ฌ์ฉํ ์ ์๋๋ก .setAllowedOrigins("*")์ ์ผ๋ค.
(3) ChatController
@RequiredArgsConstructor
@RestController
@RequestMapping("/chat")
public class ChatController {
private final ChatService chatService;
@PostMapping
public ChatRoom createRoom(@RequestParam String name) {
return chatService.createRoom(name);
}
@GetMapping
public List<ChatRoom> findAllRoom(){
return chatService.findAllRoom();
}
}
์ปจํธ๋กค๋ฌ๋ ์ฐ๋ฆฌ๊ฐ ๊ธฐ๋ณธ์ ์ธ ์ปจํธ๋กค๋ฌ์์ ์ฌ์ฉํ๋ ๋ ์์ด๋ ๋ค๋ฅธ ์ ์ด ์๋ค. ์ฌ๊ธฐ์๋ PostMapping์ผ๋ก ๋ค์ด์ค๋ฉด, ์ฑํ ๋ฐฉ์ ์์ฑํ์ฌ ์ฑํ ๋ฐฉ ์ด๋ฆ์ ๋ฐํํ๋ค. GetMapping์ผ๋ก ๋ค์ด์ค๋ฉด, ๋ชจ๋ ์ฑํ ๋ฐฉ์ ์กฐํํ์ฌ, ์ฑํ ๋ฐฉ ๊ฐ์ฒด ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ค.
โ @RequestParam์ด๋?
ํ๋ผ๋ฏธํฐ์ ์ด๋ฆ์ผ๋ก ๋ณ์์ ๋ฐ์ธ๋ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค. ๋๋ ์ด๊ฒ์ด GetMapping ๋ค ์ชฝ์ StringQuery๋ฌธ์ ๋ํด์๋ง ๊ฐ๋ฅํ๋ค๊ณ ์๊ฐํ๋๋ฐ, ์๋์๋ค. PostMapping์ด์ด๋, RequestBody์ "ํค:๊ฐ"ํํ๋ก ๊ฐ๋ค์ด ๋ค์ด์๋ค๋ฉด, ํค์ ์ด๋ฆ๊ณผ ๊ฐ์ ๋ณ์๋ฅผ ๋ฐ์ธ๋ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅ ํ๋ค. ์์ ์์์์๋ PostMapping์์ @RequestParam์ ์ฌ์ฉํ์ฌ, ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์ธ๋ฉ ํ์๋ค.
(4) DTO๋ค์ ๋ํ์ฌ
โ ChatMessage
/*
* ํด๋ผ์ด์ธํธ๋ค์ ์๋ฒ์ ์ ์ํ๋ฉด ๊ฐ๋ณ์ WebSocket Session์ ๊ฐ์ง๊ฒ ๋๋ค.
* ๋ฐ๋ผ์ ์ฑํ
๋ฐฉ์ ์
์ฅ ์ ํด๋ผ์ด์ธํธ๋ค์ WebSocket Session ์ ๋ณด๋ฅผ ์ฑํ
๋ฐฉ์ ๋งตํ ์์ผ์ ๋ณด๊ดํ๊ณ ์์ผ๋ฉด,
* ์๋ฒ์ ์ ๋ฌ๋ ๋ฉ์์ง๋ฅผ ํน์ ๋ฐฉ์ ๋งคํ๋ WebSocket ์ธ์
์ ๋ถ์๊ฒ ๋ณด๋ผ ์ ์์. -> ์ด๊ฒ์ด ๋ฉ์ธ์ง๋ฅผ ๊ตฌํํ ๋ชจ์ต
* ์ด๊ฒ์ผ๋ก ๊ฐ๋ณ ์ฑํ
๋ฐฉ์ ๊ตฌํํ ์ ์๋ค.
* */
// ์ฑํ
๋ฉ์ธ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํ DTO
@Getter
@Setter
public class ChatMessage {
// ๋ฉ์์ง ํ์
: ์
์ฅ, ์ฑํ
-> ์๋ฒ์์ ํด๋ผ์ด์ธํธ์ ํ๋์ ๋ฐ๋ผ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํ ๋ฉ์ธ์ง๋ค์ ์ ์ธํ ๊ฒ
public enum MessageType {
ENTER, TALK
}
private MessageType type; // ๋ฉ์ธ์ง ํ์
private String roomId; // ๋ฐฉ ๋ฒํธ
private String sender; // ๋ฉ์ธ์ง ๋ณด๋ธ ์ฌ๋
private String message; // ๋ฉ์ธ์ง
}
์๋ ์ฌ์ฉํ๋ DTO์์ ์ฐจ์ด์ ์ enum์ ํตํด Message์ ํ์ ์ ์์ฑํ๋ค๋ ์ ์ด๋ค. ์์ Handler์์ ํด๋น Type์ ์ผ๋ ๋ถ๋ถ์ ๋ค์ ๋ณด์.
// 1. ์น์์ผ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฉ์ธ์ง ๋ด์ฉ์ ์ ๋ฌ๋ฐ์ ์ฑํ
๋ฉ์ธ์ง ๊ฐ์ฒด๋ก ๋ณํ
ChatMessage chatMessage = objectMapper.readValue(payLoad, ChatMessage.class);
// 2. ์ ๋ฌ ๋ฐ์ ๋ฉ์ธ์ง์ ๋ด๊ธด ์ฑํ
๋ฐฉ ID๋ก ๋ด ๋ฉ์ธ์ง๋ฅผ ์ ์กํด์ผ ํ๋ ์ฑํ
๋ฐฉ ๊ฐ์ฒด๋ฅผ ๋ถ๋ฌ์จ๋ค.
ChatRoom room = chatService.findRoomById(chatMessage.getRoomId());
// 3. ํด๋น ์ฑํ
๋ฐฉ์ ์
์ฅํด์๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ๋ค(Websocket session)์๊ฒ ํ์
์ ๋ฐ๋ฅธ ๋ฉ์ธ์ง ๋ฐ์ก
room.handleActions(session, chatMessage, chatService);
3๋ฒ์ ๋ณด๋ฉด room์ ํ๋์ chatMessage์ MessageType์ด ๋ฌด์์ธ๊ฐ์ ๋ฐ๋ผ ํ๋์ด ๋ฌ๋ผ์ง๋ค.
โก ChatRoom
import lombok.Builder;
import lombok.Getter;
import org.springframework.web.socket.WebSocketSession;
import org.websocket.demo.service.ChatService;
import java.util.HashSet;
import java.util.Set;
// ์ฑํ
๋ฐฉ DTO
@Getter
public class ChatRoom {
// ์ฑํ
๋ฐฉ์ ๋ฐฉ ๋ฒํธ์ ์ด๋ฆ
private String roomId;
private String name;
// ํด๋น ์ฑํ
๋ฐฉ์ ์
์ฅํ ํด๋ผ์ด์ธํธ๋ค์ ์ธ์
๋ฆฌ์คํธ
private Set<WebSocketSession> sessions = new HashSet<>();
// ์ปค์คํฐ๋ง์ด์งํ ์์ฑ์ -> @ArgsConstructor ์ฐ๋ฉด ์๋จ! ๊ทธ๊ฑฐ๋ ๋ชจ๋ ๋ฉค๋ฒ ๋ณ์์ ๋ํด์ ๋งคํ์ ์งํํ๊ธฐ ๋๋ฌธ์!!
@Builder
public ChatRoom (String roomId, String name) {
this.roomId = roomId;
this.name = name;
}
// ์ฑํ
๋ฐฉ์์ ํ๋ ํ๋์๋ ํฌ๊ฒ ์
์ฅํ๊ธฐ์ ๋ํํ๊ธฐ ๊ธฐ๋ฅ ๋ ๊ฐ๊ฐ ์๋ค.
// ๋ฐ๋ผ์ ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ค ํ๋์ ํ๋๋์ ๋ฐ๋ผ ๋ถ๊ธฐ์ฒ๋ฆฌ๋ฅผ ํด์ค์ผ ํ๋ค.
// ๊ทธ๊ฒ์ ์ํ ๋งค์๋์ด๋ค.
// ์
์ฅ ์: ์ฑํ
๋ฃธ์ session ์ ๋ณด๋ฅผ ํด๋ผ์ด์ธํธ์ session ๋ฆฌ์คํธ์ ์ถ๊ฐ
// ์ฑํ
์: ์ฑํ
๋ฃธ์ ๋ฉ์ธ์ง๊ฐ ๋์ฐฉํ ๊ฒฝ์ฐ ์ฑํ
๋ฃธ์ ๋ชจ๋ session์ ๋ฉ์์ง ๋ฐ์ก
public void handleActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService){
if(chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) {
sessions.add(session);
chatMessage.setMessage(chatMessage.getSender() + "๋์ด ์
์ฅํ์ต๋๋ค.");
}
sendMessage(chatMessage, chatService);
}
public <T> void sendMessage(T message, ChatService chatService) {
sessions.parallelStream().forEach(session -> chatService.sendMessage(session, message));
}
}
โก - โ @Builder๋?
์์ฑ์ ํจํด์ด ์๋ ๋น๋- ํจํด์ผ๋ก ๊ฐ์ฒด ์์ฑ ๋ฐ ์ด๊ธฐํ ํ ๋, ํ์ํ ์ด๋ ธํ ์ด์ ์ด๋ค. ๋จผ์ ๋น๋ ํจํด์ด ๋ฌด์์ธ์ง ์์๋ณด์. ๋ง์ฝ ์ฐ๋ฆฌ๊ฐ ์์ฑ์๋ฅผ ํตํด์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์ด๊ธฐํํ๋ ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ ์์ฑ์๊ฐ ํ์ํ ๊ฒ์ด๋ค.
Bag bag = new Bag("name", 1000, "memo");
๋ง์ฝ ๋น๋ ํจํด์ ํตํด ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์ด๊ธฐํ ํ๋ ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ํ๋ฉด ๋๋ค.
Bag bag = Bag.builder()
.name("name")
.money(1000)
.memo("memo")
.build();
๊ฐ์ฒด๋ฅผ ์์ฑํ ์ ์๋ ๋น๋๋ฅผ builder() ํจ์๋ฅผ ํตํด์ ์ป๋๋ค. ๊ทธ ์์ ๋ฉค๋ฒ ๋ณ์ ๋ณ๋ก ์ด๊ธฐํํ ๊ฐ์ ์ ์ด์ฃผ๊ณ , ๋ง์ง๋ง์ .build()๋ฅผ ์ฌ์ฉํ๋ฉด, ํด๋น ๊ฐ๋ค์ ๊ฐ์ง ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ค.
๊ทธ๋ ๋ค๋ฉด Builder Pattern์ ์ฌ์ฉํด์ผํ๋ ์ด์ ๋ ๋ฌด์์ผ๊น?
๋น๋ ํจํด์ ๊ฒฝ์ฐ ์์ฑ์๋ณด๋ค ์ฅ์ ์ด ๋ง๋ค.
- ์์ฑ์ ํจํด๋ณด๋ค ๊ฐ๋
์ฑ์ด ์ข๋ค.์ด๋ค ๋ณ์์ ๋ํ ์ด๊ธฐํ์ธ์ง ํ ๋์ ์ ์ ์๋๊ฐ? ์์ฑ์ ํจํด ์ด๊ธฐํ๋ง ๋ณด๊ณ ๋ ์๊ธฐ๊ฐ ์ฝ์ง ์๋ค. ํ์ง๋ง builder ํจํด์ผ๋ก ํ๊ฒ ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ด ์๋ค.๋ฌด์จ ๋ฉค๋ฒ๋ณ์์ ๋ํ ์ด๊ธฐํ์ธ์ง ํ ๋์ ์ ์ ์๋ค
Bag bag = new Bag("name", 1000, "memo", "abc", "what", "is", "it", "?");
Bag bag = Bag.builder() .name("name") .money(1000) .memo("memo") .letter("This is the letter") .box("This is the box") .build();
์์ ์์ฑ์ ํจํฐ์ ๊ฒฝ์ฐ, ์ด๊ธฐํ ํ๋ ๊ฐ์ด 3๊ฐ ๋ฟ์ด๋ผ์ ๋ณด๊ธฐ ์ฝ์ง๋ง ์ด๊ธฐํํ ๊ฐ์ด ์ฌ๋ฌ ๊ฐ์ผ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด๋ผ. - Builder Pattern์ ๊ฒฝ์ฐ, ๋ฉค๋ฒ ๋ณ์ ์ด๊ธฐํ์ ์์๊ฐ ์กด์ฌํ์ง ์๋๋ค.
์์ฑ์์ ๊ฒฝ์ฐ, ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํ ํ๊ธฐ ์ํด ๋ฃ์ด์ผ ํ๋ ๊ฐ์ ์์๊ฐ ์กด์ฌํ๊ณ , ์ด๋ฅผ ์งํค์ง ์์์ ๋๋ ์๋ฌ๊ฐ ๋๊ฑฐ๋ ์๋ฑํ ๊ฐ์ด ๋ค์ด๊ฐ๋ค.
public Bag(String name, int money, String memo) {
this.name = name;
this.money = money;
this.memo = memo;
}
ํ์ง๋ง ๋น๋ ํจํด์ ๊ฒฝ์ฐ, ํ๋ ์ด๋ฆ ๋ณ๋ก ๊ฐ์ ๋ช ์ํ๊ธฐ ๋๋ฌธ์ ์์๊ฐ ์๊ด ์๋ค. ๋ฐ๋ผ์ ์ฝ๋ฉ ์ ํจ์ฌ ํธํ๋ค.
Bag bag = Bag.builder()
.name("name")
.memo("memo") // memo๋ฅผ money ๋์ ๋จผ์ !
.money(1000)
.build();
ํ์ง๋ง Builder ํจํด ์ฐ๊ธฐ์ ์ด๋ ค์ด ์ ...
๋น๋ ํจํด์ ์ฅ์ ์ ๋ณด๋ฉด ์์ฒญ ๋ง์ง๋ง, ์ ์์ฃผ ์ ๋ณด์ผ๊น? ๊ทธ ์ด์ ๋ ํด๋์ค ๋ด์์ Builder ํจ์๋ฅผ ์ปค์คํฐ๋ง์ด์ง ํด์ ๋ง๋ค์ด์ผ ํ๋๋ฐ ๊ทธ ๊ณผ์ ์ด ๊ทน์
์ ๋์ด๋์ด๊ธฐ ๋๋ฌธ์ด๋ค. ์์ ์์ ๋ฅผ ๋ณด๋ฉด ๋ฉค๋ฒ ๋ณ์ ์ด๋ฆ๋ณ๋ก mapping์ด ๋์๋ค. ์ด๋ ๊ฒ ๋ฉค๋ฒ ๋ณ์ ๋ณ๋ก ๊ฐ์ mapping ์ํค๋ ํจ์๋ ์ ๋ถ ๋ง๋ค์ด์ผ ํ๊ณ , Builder ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ์์ฑ์ ๋ํ ์ค์ค๋ก ๋ง๋ค์ด์ผ ํ๋ค!
ํ์ง๋ง ๊ณ ๋ง๊ฒ๋... LOMBOK์ ํด๋น Builder๋ฅผ ์๋์ผ๋ก ์์ฑํด์ฃผ๋ ์ด๋
ธํ
์ด์
์ ๊ฐ์ง๊ณ ์๊ณ ๊ทธ๊ฒ์ด @Builder ์ด๋ค! ๋ค์ ์์๋ @Builder ์ด๋
ธํ
์ด์
์ ์ผ์ ์, ์ ์ ์ผ์ ์๋ฅผ ๋๋์ด ๋์ ๊ฒ์ด๋ค. ์ด๋
ธํ
์ด์
์ ํธ๋ฆฌํจ์ ์ค๊ฐํด๋ด๋ผ
// ๋น๋ ์ด๋
ธํ
์ด์
์ ์ธ ๊ฒฝ์ฐ ----------------------------------------------------------------------------
@Builder
class Example<T> {
private T foo;
private final String bar;
}
// ์ ์ธ ๊ฒฝ์ฐ -----------------------------------------------------------------------------------------
class Example<T> {
private T foo;
private final String bar;
private Example(T foo, String bar) {
this.foo = foo;
this.bar = bar;
}
public static <T> ExampleBuilder<T> builder() {
return new ExampleBuilder<T>();
}
public static class ExampleBuilder<T> {
private T foo;
private String bar;
private ExampleBuilder() {}
public ExampleBuilder foo(T foo) {
this.foo = foo;
return this;
}
public ExampleBuilder bar(String bar) {
this.bar = bar;
return this;
}
@java.lang.Override public String toString() {
return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
}
public Example build() {
return new Example(foo, bar);
}
}
}
โก - โ handleAction์ ๋ํด
public void handleActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService){
if(chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) {
sessions.add(session);
chatMessage.setMessage(chatMessage.getSender() + "๋์ด ์
์ฅํ์ต๋๋ค.");
}
sendMessage(chatMessage, chatService);
}
public <T> void sendMessage(T message, ChatService chatService) {
sessions.parallelStream().forEach(session -> chatService.sendMessage(session, message));
}
}
์์ ๋งค์๋๋ ๋ฉค๋ฒ ๋ฉ์๋์ด๋ค. ํด๋น ๋งค์๋๋ ์ธ์๋ก ๋ค์ด์จ chatMessage์ MessageType์ ๋ฐ๋ผ ํด์ผํ ์ผ์ ๋๋ ์ ๊ด๋ฆฌํ๊ณ ์๋ค. ํ์ ์ด "ENTER"์ผ ๊ฒฝ์ฐ, ~~๋์ด ์ ์ฅํ์ต๋๋ค ๋ผ๋ ๋ฌธ์์ด์ ๋ชจ๋ ์ฑํ ๋ฐฉ ๋ฉค๋ฒ์๊ฒ ์ ์กํ๋ค. ๊ทธ ์ธ์ ๊ฒฝ์ฐ๋ ๊ทธ๋ฅ ๋ฉ์ธ์ง๋ฅผ ์ ๋ฌํ๋ค.
๋ฉ์ธ์ง ์ ๋ฌํจ์๋ฅผ ๋ฐ๋ก ๋บด์ ์ ์ํ์๋๋ฐ, Stream์ ์ฌ์ฉํด์ ํ์ฌ ๋ฆฌ์คํธ์ ๋ ์ธ์ ํ๋ํ๋์๊ฒ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ธ๋ค.
์๊ฒฌ
์ฌ๊ธฐ์ ๋ณด๋ฉด DTO ์์ ๋งค์๋๋ฅผ ์ ์ํ๋ฉด์ Controller - service ๊ด๊ฒ๋ฅผ ์ ๋๋ก ์งํค์ง ์์๋ค๋ ์๊ฐ์ด ๋ ๋ค. ํ์ง๋ง ์์ webSockConfig๋ผ๋ ์ปจํธ๋กค๋ฌ ์ญํ ์ ํ๋ ๋ ์์์ Dto ๊ฐ์ฒด๋ฅผ ๋ง๋ ํ ๊ทธ ์์ ๋งค์๋๋ฅผ ๋ฐ๋ก ์จ์ผํ๋ ์ํฉ์ด ๋์ค๊ธด ํ๋ค. ์ด์ฒ๋ผ ์ปจํธ๋กค๋ฌ ์ญํ ์ ํ๋ ๋ ์์ด ๋ ๋ช ์ด๋ผ ์ ์์ ์ธ MVC ํจํด์ ์งํค๊ธฐ ์ด๋ ค์ ๋์ง ์๋๋ฉด, ์ฝ๋ ์์ฑํ์ ๋ถ์ด ํธ์์ ๋ฐ๋ผ ์ด๋ ๊ฒ ํ์ จ๋์ง ๋ถ๊ฐ์ ์๋์ง๋ง, ๋ด๊ฐ ๋์ค์ ์ค์ค๋ก ๋ง๋ค ๋๋ ๊ฐ ๋๋ ํ ๋ฆฌ ๋ณ ํด์ผํ ๊ธฐ๋ฅ์ ์ ํํ ์ง์ผ์ ๋ค์ ์์ฑํด๋ด์ผ๊ฒ ๋ค.
(5) ChatService
@Slf4j
@RequiredArgsConstructor
@Service
public class ChatService {
private final ObjectMapper objectMapper;
// ์๋ฒ์ ์์ฑ๋ ๋ชจ๋ ์ฑํ
๋ฐฉ์ ์ ๋ณด๋ฅผ ๋ชจ์๋ ๊ตฌ์กฐ์ฒด. (์ฑํ
๋ฐฉ์ ์ ๋ณด ์ ์ฅ์ ๋น ๋ฅธ ๊ตฌํ์ ์ํด ์ผ๋จ DB ์ ์ฐ๊ณ HashMap ์ ์ฅ์ผ๋ก ๊ตฌํ)
private Map<String, ChatRoom> chatRooms;
// ์์ฑ์ ์ด์ฉํด ์์ฑ๋ ํ์ ํ ์ผ์ ์ ์
@PostConstruct
private void init() {
chatRooms = new LinkedHashMap<>();
}
// ๋ชจ๋ ๋ฐฉ ์กฐํ
public List<ChatRoom> findAllRoom() {
return new ArrayList<>(chatRooms.values());
}
// ๋ฐฉ ๋ฒํธ๋ฅผ ์ด์ฉํ ๋ฐฉ ์ฐพ๊ธฐ
public ChatRoom findRoomById(String roomId) {
return chatRooms.get(roomId);
}
// ๋ฐฉ ์์ฑ
public ChatRoom createRoom(String name) {
// ๋ฐฉ๋ฒํธ๋ก ์ธ ์์ด๋ ์์ฑ
String randomId = UUID.randomUUID().toString();
// ์ฑํ
๋ฐฉ (1 ๊ฐ) ์์ฑ
ChatRoom chatRoom = ChatRoom.builder()
.roomId(randomId)
.name(name)
.build();
//์์ฑ๋ ์ฑํ
๋ฐฉ์ ์ฑํ
๋ฐฉ ๋ฆฌ์คํธ์ ๋ฃ๊ธฐ
chatRooms.put(randomId, chatRoom);
return chatRoom;
}
// ์ธ์
ํ๋์ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ด๋ ๋งค์๋
public <T> void sendMessage(WebSocketSession session, T message) {
try {
session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(message)));
}catch (IOException e){
log.error(e.getMessage(), e);
}
}
}
โ @PostConstruct๋?
Spring์ด ํด๋น ํด๋์ค๋ฅผ bean ๊ฐ์ฒด๋ก ๋ง๋ ํ์ ๋ฑ ํ๋ฒ๋ง @PostConstruct๊ฐ ๋ฌ๋ฆฐ ๋งค์๋๋ฅผ ํธ์ถํ์ฌ ์คํํ๋ค. ํ๋ก ํธ์์ ๋ฐฐ์ ๋, ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์๊ฐํ๋ฉด ๋ ์ดํดํ๊ธฐ ์ฌ์ธ ๊ฒ์ด๋ค.
โ UUID๋?
๋คํฌ์ํฌ ์์์ ์ ์ผ์ฑ์ด ๋ณด์ฅ๋๋ ID๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๊ท์ฝ์ด๋ค. (Universe Unique Identifier)
UUID๋ ๋ถ์ฐ ๋คํฌ์ํฌ ํ๊ฒฝ์์ ์ฌ์ฉ๋๋ ID๋ก, ๋ง์ฝ ์ค์ ๊ด๋ฆฌ ์์คํ
์ด ์๋ ํ๊ฒฝ์ด๋ฉด, ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ์ธ์
๋ง๋ค ์ผ๋ ฌ๋ฒํธ๋ฅผ ๋ถ์ฌํด์, ๋ชจ๋ ํด๋ผ์ด์ธํธ๋ค์ ๋ํด์ ์ ์ผ์ฑ์ ๋ณด์ฅํ ์ ์๊ฒ ๋ค. ํ์ง๋ง ๋ถ์ฐ ๋คํฌ์ํฌ ํ๊ฒฝ์ด๋ฉด, ์ผ๋ฅด๋ฒ ๋ก์ค์ฒ๋ผ ๋จธ๋ฆฌ๊ฐ ๋๋ ๊ด๋ฆฌ ์์คํ
์ด ์ฌ๋ฌ ๊ฐ์ผ ์ ์๊ธฐ ๋๋ฌธ์, ๊ฐ ๋จธ๋ฆฌ๊ฐ ํด๋ผ์ด์ธํธ์ ์ธ์
์ ๊ณ ์ ๋ฒํธ๋ฅผ ์ค๋ค๋ ๋ณด์ฅ์ด ์๋ค. ์ด๋ ์ฌ์ฉํ๋ ๊ฒ์ด UUID์ด๋ค.
โ WebSocketSession
public <T> void sendMessage(WebSocketSession session, T message) {
try {
session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(message)));
}catch (IOException e){
log.error(e.getMessage(), e);
}
}
์ด ๋ถ๋ถ์ ๋ํด ํด์ํ๊ฒ ๋ค.
websocksession์ ๊ฒฝ์ฐ, sendMessage๋ผ๋ ๋งค์๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ํด๋น ๋งค์๋๋ฅผ ํตํด ํด๋น ์ธ์
์ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ผ ์ ์๋ค. ๋ฉ์ธ์ง๋ Message ๊ฐ์ฒด ํํ์ด์ด์ผ ํ๊ณ , ์ด๋ Message์ payload๋ ์ง๋ ฌํ ์ํ์ฌ์ผ ํ๋ค.
3. ์์ฐ
(1) ์ฑํ ๋ฐฉ ์์ฑ
๋จผ์ PostMan์ ํตํด Post ์์ฒญ์ ๋ณด๋ด์ ์ฑํ ๋ฐฉ์ ์์ฑํ๋ค.
์ฐ๋ฆฌ๋ ๊ฒฐ๊ณผ๊ฐ์ผ๋ก ๋ฐ์ ๋ฐฉ๋ฒํธ๋ฅผ ํตํด์ ์ ์ํด์ผํ๋ค. ์์ง ํ๋ก ํธ ์๋๋ ๊ตฌํํ์ง ์์์์ผ๋ก, ๋ฐฉ๋ฒํธ๋ ์ฐ๋ฆฌ๊ฐ ์ง์ ๋ณต์ฌํ์ฌ ํด๋ผ์ด์ธํธ ์ ์ฅ์ ์ฌ์ฉํ๋ค.
(2) ์ฑํ ๋ฐฉ ์ ์ฅ
์ฐ๋ฆฌ๋ ์์ง ํ๋ก ํธ ์๋ ๊ตฌํ์ด ์๋์ด์๊ธฐ ๋๋ฌธ์ ํฌ๋กฌ ํ์ฅ์ ์ค ํ๋์ธ 'WebSocketClient'๋ฅผ ์ด๋ค. ์ค์๊ฐ ์ฑํ
ํ์ธ์ ์ํด ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ ๊ฐ ๋์ฐ๊ณ ์ ์ชฝ์์ ์ ๋ถ ์ฑํ
๋ฐฉ์ ์
์ฅํ๋ค. ์์์ ๋ดค๋ฏ์ด ์
์ฅ์ ์ํ Message Type์ ENTER์๋ค. ์ ์ ํ JSON์ ๋ง๋ค์ด์ ์
์ฅํ๊ฒ ๋ค.
์๊น webSockConfig ํ์ผ์์ ์ ์ ํด๋์๋, /ws/chat์ด ์น์์ผ ์ฑํ
๋ฐฉ์ ์ธ ์ ์๋ ์ฃผ์ ์ด๋ค.
์ผ๋จ ์ด๊ฑฐ ๊น์๋ผ
ํด๋น ๋ฐฉ๋ฒํธ๋ก ์ ์ฅํ๊ฒ ๋ค.
์ ์ฅ ์ฑ๊ณตํ๋ค.
์ด๋ฒ์ 2๋ฒ์งธ ํด๋ผ์ด์ธํธ๋ฅผ ์ ์ฅ์์ผ๋ณด๊ฒ ๋ค. 2๋ฒ์งธ ํด๋ผ์ด์ธํธ๊ฐ ๋ค์ด์ค๋ฉด ์ ๊ฐ๋์ ๋ฐ๋ผ 2๋ฒ์งธ ํด๋ผ์ด์ธํธ๊ฐ ์ ์ฅํ๋ค๋ ๋ฉ์ธ์ง๊ฐ ์ฐจ๋ฌด์๊ณผ ํด๋ผ์ด์ธํธ 2 ์ ๋ถ์๊ฒ ๊ฐ์ผํ๋ค.
ํด๋ผ์ด์ธํธ 2 ์ด๋ฆ์ ํ์ฑํ์ธ๋ฐ ํ์ฑํ๊ฐ ์ ์ฅํ์์ด ์์ชฝ ๋ค์๊ฒ ๋ด๋ค.
๋ง์ง๋ง์ผ๋ก TALK์ ํด๋ณด๊ฒ ๋ค. ์ฐจ๋ฌด์์ด ํ์ฑํํํ ํ๋ฐ์ ํ๋๋ก ํด๋ณด๊ฒ ๋ค. ๋ฉ์ธ์ง ํ์ ์ TALK์ผ๋ก ๋ฐ๊พผ๋ค.
ํ๋ฐ ๋ฌธ์๊ฐ ์ ๊ฐ๋ค.
๋ค์์ ์น ์์ผ์ ํ๋ก ํธ์๋๋ ์ถ๊ฐํด์ ๋ ์ฌํ๋ฒ์ ผ์ ๋ง๋ค์ด๋ณด๊ฒ ๋ค. ํด๋น ๋ด์ฉ ์ค๋ช
์ STEP2 branch์ ์ฌ๋ฆฌ๊ณ ๋ง๋ค๊ฒ ๋ค.