1. pom.xml 내용추가
<!-- websocket api -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.3.22.RELEASE</version>
</dependency>
  1. 웹소켓 핸들러 커스터마이징
package kr.or.ddit.common.chat.handler;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Inject;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.fasterxml.jackson.databind.ObjectMapper;

import kr.or.ddit.common.chat.dao.ChatReadStatusDAO;
import kr.or.ddit.common.chat.dao.ChatRoomMsgDAO;
import kr.or.ddit.common.chat.service.ChatRoomService;
import kr.or.ddit.common.chat.vo.ChatReadStatusVO;
import kr.or.ddit.common.chat.vo.ChatRoomMemberVO;
import kr.or.ddit.common.chat.vo.ChatRoomMsgVO;
import kr.or.ddit.common.chat.vo.ChatRoomVO;
import kr.or.ddit.common.vo.AccountWrapper;
import kr.or.ddit.common.vo.EmployeeVO;
import kr.or.ddit.operate.vo.AirlineVO;
import kr.or.ddit.operate.vo.vendorVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class CustomSocketHandler extends TextWebSocketHandler {
	
	@Inject
	private ChatRoomService chatRoomService;
	
	@Inject
	private ChatRoomMsgDAO chatRoomMsgDao;
	
	@Inject
	private ChatReadStatusDAO chatReadStatusDao;
	
	// 로그인중인 개별유저
	Map<String, WebSocketSession> users = new ConcurrentHashMap<String, WebSocketSession>();
	
	// 클라이언트가 서버로 연결시
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		Map<String, String> userInfo = getMemberId(session); // 접속한 유저의 http세션을 조회하여 유저정보를 얻는 함수
		String senderId = userInfo.get("memId"); 
		if (senderId != null && !users.containsKey(senderId)) { // 로그인 값이 있는 경우만
			log.info(senderId + " 연결됨");
			users.put(senderId, session); // 로그인중 개별유저 저장
		}
	}

	// 클라이언트가 Data 전송 시
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		Map<String, String> userInfo = getMemberId(session); // 접속한 유저의 http세션을 조회하여 유저정보를 얻는 함수
		String senderId = userInfo.get("memId");
		String empNm = userInfo.get("memName");
		
		ObjectMapper objectMapper = new ObjectMapper();
		
		// 송신자가 보낸내용들
		Map<String, String> mapReceive = objectMapper.readValue(message.getPayload(), Map.class);
		
		String type = mapReceive.get("type");
		String msg = mapReceive.get("msg");
		
		if (msg == null) return;
		
		// 수신자에게 보낼 내용들
		Map<String, String> mapToSend = new HashMap<String, String>();
		
		String jsonStr = null;
		
		switch (type) {
		case "CHAT_REQ":
			mapToSend.put("type", "chat");
			// 같은 채팅방에 메세지 전송
			LocalDateTime now = LocalDateTime.now();
			// 초의 소수점을 없애기 위해 truncatedTo() 사용
	        LocalDateTime sendTime = now.truncatedTo(ChronoUnit.SECONDS);
	        // 보내는 값에 msg, bang_id 필요
	        
	        // 채팅방아이디를 가지고 채팅방 정보 가져오기
	        String chrId = mapReceive.get("bang_id");
	        ChatRoomVO chatRoomInfo = chatRoomService.retrieveRoomDetail(chrId);
	        if (chatRoomInfo == null) return;
	        
	        // 채팅방 소속 멤버들 중에 현재 접속되어 있는 사람들에게 실시간 메시지 전송 
	        for (ChatRoomMemberVO chatRoomMember : chatRoomInfo.getChatRoomMembers()) {
	        	String accId = (chatRoomMember.getEmpNo());
	        	if (users.containsKey(accId)) {
	        		WebSocketSession targetSession = users.get(accId);
	        		mapToSend.put("bang_id", chrId);
	        		mapToSend.put("sender", senderId);
	        		mapToSend.put("sendTime", sendTime.toString());
					mapToSend.put("msg", empNm + " : " + mapReceive.get("msg") + " [" + sendTime + "]");
					jsonStr = objectMapper.writeValueAsString(mapToSend);
					targetSession.sendMessage(new TextMessage(jsonStr));
	        	}
	        }
	        
			// chatRoomMsgVO 생성해서 저장하기
			// 필요정보 내용, 누가 보냈는지
			ChatRoomMsgVO chatRoomMsg = new ChatRoomMsgVO();
			chatRoomMsg.setCrmId(UUID.randomUUID().toString().substring(0, 30));
			chatRoomMsg.setCrmContent(msg);
			chatRoomMsg.setCrmCrtTs(sendTime);
			chatRoomMsg.setChrId(mapReceive.get("bang_id"));
			chatRoomMsg.setEmpNo(senderId);
			chatRoomMsgDao.insertMessage(chatRoomMsg);
			
			// 내가 작성한 내용 바로 읽음처리하기
			List<ChatReadStatusVO> readStatusList = new ArrayList<>();
			ChatReadStatusVO readStatus = new ChatReadStatusVO();
			readStatus.setCrsEmpNo(senderId);
			readStatus.setCrsMsgId(chatRoomMsg.getCrmId());
			readStatusList.add(readStatus);
			chatReadStatusDao.insertChatReadStatuses(readStatusList);
			
			break;
		// 보안요청 도착시 보안부서 직원들에게만 알림 보내기
		case "SECURITY_REQ":
			mapToSend.put("type", "security");
			mapToSend.put("msg", "[보안요청]" + msg);
			jsonStr = objectMapper.writeValueAsString(mapToSend);
			for (Map.Entry<String, WebSocketSession> entry : users.entrySet()) {
			    String memId = entry.getKey();
			    // 보안부서번호 : 1203
			    String deptNo = memId.substring(0, 4);
			    if (deptNo.equals("1203")) {
			    	WebSocketSession targetSession = entry.getValue();
			    	targetSession.sendMessage(new TextMessage(jsonStr));
			    }
			}
			break;
		// 수리요청 도착시 인프라부서 직원들에게만 알림 보내기
		case "REPAIR_REQ":
			mapToSend.put("type", "repair");
			mapToSend.put("msg", "[수리요청]" + msg);
			jsonStr = objectMapper.writeValueAsString(mapToSend);
			for (Map.Entry<String, WebSocketSession> entry : users.entrySet()) {
			    String memId = entry.getKey();
			    // 인프라부서번호 : 1204
			    String deptNo = memId.substring(0, 4);
			    if (deptNo.equals("1204")) {
				    WebSocketSession targetSession = entry.getValue();
				    targetSession.sendMessage(new TextMessage(jsonStr));
			    }
			}
			break;
		}
	}

	// 연결 해제될 때
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		Map<String, String> userInfo = getMemberId(session);
		String senderId = userInfo.get("memId");
		if(senderId != null) {	// 로그인 값이 있는 경우만
			log(senderId + " 연결 종료됨");
			users.remove(senderId);
		// sessions.remove(session);
		}
	}

	// 에러 발생시
	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		log(session.getId() + " 익셉션 발생: " + exception.getMessage());
	}

	// 로그 메시지
	private void log(String logmsg) {
		System.out.println(new Date() + " : " + logmsg);
	}

	// 웹소켓에서 유저정보 가져오기
	// 접속한 유저의 http세션을 조회하여 id를 얻는 함수
	private Map<String, String> getMemberId(WebSocketSession session) {
		
		Map<String, String> userInfo = new HashMap<>();
		
		SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = (Authentication) session.getPrincipal();

        // principal 객체에서 현재 로그인한 직원의 사번 찾기
 		AccountWrapper aw = (AccountWrapper<?>) authentication.getPrincipal();
 		String accInfo = aw.getAccInfo();
 		
 		String memId = null;
 		String memName = null;
 		if (accInfo.equals("AL")) {
			// 항공사계정
 			AirlineVO airLine = (AirlineVO) aw.getRealUser();
 			memId = airLine.getAlCd();
 			memName = airLine.getAlKnm();
		} else if (accInfo.equals("VD")) {
			// 입점업체계정
			vendorVO vendor = (vendorVO) aw.getRealUser();
			memId = vendor.getVendId();
			memName = vendor.getVendNm();
		} else {
			// 공항직원계정
			EmployeeVO employee = (EmployeeVO) aw.getRealUser();
			memId = employee.getEmpNo();
			memName = employee.getEmpNm();
		}
 		
 		userInfo.put("memId", memId);
 		userInfo.put("memName", memName);
 		
		return userInfo;
	}
}
  1. websocket-config.xml 작성

Untitled

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="<http://www.springframework.org/schema/beans>"
	xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
	xmlns:websocket="<http://www.springframework.org/schema/websocket>"
	xsi:schemaLocation="<http://www.springframework.org/schema/beans> <http://www.springframework.org/schema/beans/spring-beans.xsd>
		<http://www.springframework.org/schema/websocket> <http://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd>">

    <bean id="customSocketHandler" class="kr.or.ddit.common.chat.handler.CustomSocketHandler" />

	<websocket:handlers allowed-origins="*">
	<websocket:mapping handler="customSocketHandler" path="/chat" />
	<websocket:mapping handler="customSocketHandler" path="/alarm" />
	<websocket:handshake-interceptors>
		<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor" />
	</websocket:handshake-interceptors>
	<websocket:sockjs />
	</websocket:handlers>
</beans>
  1. web.xml 설정추가
<!-- The front controller of this Spring Web application, responsible for 
		handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		/WEB-INF/spring/servlet-context.xml
		/WEB-INF/spring/websocket-config.xml
	</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
  1. 클라이언트에서 소켓연결 및 받는 방법
<!-- sockJS -->
<script src="<https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js>"></script>
<script>
// 전역변수 설정
var socket  = null;
$(document).ready(function(){
    // 웹소켓 연결
    sock = new SockJS("<c:url value="/chat"/>");
    socket = sock;

    // 데이터를 전달 받았을때 
    sock.onmessage = onMessage; // toast 생성
    ...
});

// toast생성 및 추가
function onMessage(evt){
    var data = evt.data;
    // toast
    let toast = "<div class='toast' role='alert' aria-live='assertive' aria-atomic='true'>";
    toast += "<div class='toast-header'><i class='fas fa-bell mr-2'></i><strong class='mr-auto'>알림</strong>";
    toast += "<small class='text-muted'>just now</small><button type='button' class='ml-2 mb-1 close' data-dismiss='toast' aria-label='Close'>";
    toast += "<span aria-hidden='true'>&times;</span></button>";
    toast += "</div> <div class='toast-body'>" + data + "</div></div>";
    $("#msgStack").append(toast);   // msgStack div에 생성한 toast 추가
    $(".toast").toast({"animation": true, "autohide": false});
    $('.toast').toast('show');
		// alert띄워서 확인하는 게 편함!
};
  1. 웹소켓을 이용한 전송 방법
<script type="text/javascript">
	var webSocket = {
		init : function(param) {
// 			this._url = param.url;
			// console.log("Url: ", this._url) //  /Airport80/chat
			this._initSocket();
		},
		sendChat : function() {
			this._sendMessage('CHAT_REQ', '${param.bang_id}', $('#message').val());
			$('#message').val('');
		},
		/* sendEnter : function() {
			this._sendMessage('${param.bang_id}', 'CMD_ENTER', $('#message').val());
			$('#message').val('');
		}, */
		receiveMessage : function(msgData) {
			let newDiv = $('<div>').text(msgData.msg).addClass("message");
			if (msgData.sender == ${authMember.empNo }) {
				newDiv.addClass("mine");
			} else {
				newDiv.addClass("other");
			}
			$('#divChatData').append(newDiv);
			scrollToBottom();
			// 정의된 CMD 코드에 따라서 분기 처리
			/* if (msgData.cmd == 'CMD_MSG_SEND') {
				$('#divChatData').append('<div>' + msgData.msg + '</div>');
			}
			// 입장
			else if (msgData.cmd == 'CMD_ENTER') {
				$('#divChatData').append('<div>' + msgData.msg + '</div>');
			}
			// 퇴장
			else if (msgData.cmd == 'CMD_EXIT') {
				$('#divChatData').append('<div>' + msgData.msg + '</div>');
			} */
		},
		/* closeMessage : function(str) {
			$('#divChatData').append('<div>' + '연결 끊김 : ' + str + '</div>');
		}, */
// 		disconnect : function() {
// 			this._socket.close();
// 		}, 
		_initSocket : function() {
			console.log("view_chat.jsp 소켓연결됐음다")
// 			this._socket = new SockJS(this._url);
			this._socket = window.opener.socket;
			console.log("socket", this._socket);
// 			this._socket.onopen = function(evt) {
// 				webSocket.sendEnter();
// 			};
			this._socket.onmessage = function(evt) {
				webSocket.receiveMessage(JSON.parse(evt.data));
			};
			/* this._socket.onclose = function(evt) {
				webSocket.closeMessage(JSON.parse(evt.data));
			} */
		},
		_sendMessage : function(type, bang_id, msg) {
			if (msg == "") {
				alert("메시지를 입력해주세요.");
				return;
			}
			var msgData = {
				type: type,
				bang_id : bang_id,
				msg : msg
			};
			var jsonData = JSON.stringify(msgData);
			this._socket.send(jsonData);
		}
	};
</script>
<script type="text/javascript">
	$(window).on('load', function() {
		webSocket.init({
			url : '<c:url value="/chat" />'
		});
	});
</script>

[Spring] Websocket / Sock js를 사용한 실시간 알림전송 기능 구현