해당 메서드 코드 :

 

테스트 코드 통해서 분석 

@Test
void getMergedAnnotationAttributesWithConventionBasedComposedAnnotation() {
			Class<?> element = ConventionBasedComposedContextConfigClass.class;
			String name = ContextConfig.class.getName();
			AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);

			assertThat(attributes).as("Should find @ContextConfig on " + element.getSimpleName()).isNotNull();
			// Convention-based annotation attribute overrides are no longer supported as of
			// Spring Framework 7.0. Otherwise, we would expect "explicitDeclaration".
			assertThat(attributes.getStringArray("locations")).as("locations").isEmpty();
			assertThat(attributes.getStringArray("value")).as("value").isEmpty();

			// Verify contracts between utility methods:
			assertThat(isAnnotated(element, name)).isTrue();
}

 

ConventionBasedComposedContextConfigClass

해당 클래스를 확인해보면 

 

해당 클래스를 location 속성에explicitDeclaration    선언한 상태입니다. 

@ConventionBasedComposedContextConfig(locations = "explicitDeclaration")
	static class ConventionBasedComposedContextConfigClass {
	}

locations 속성은 어노테이션을 적용할 때, 관련된 구성 정보(예를 들어, 애플리케이션 컨텍스트 설정 파일이나 리소스)의 위치를 지정하기 위한 문자열 배열입니다.

예를들어 

locations 속성은 XML 파일이나 기타 리소스의 경로를 지정

@ContextConfiguration(locations = "classpath:/applicationContext.xml")

아니면 마커 또는 플래그로 이용됩니다. 

 

spring -core 코드를 보면 해당 어노테이션에 locations = "explicitDeclaration" 가 존재합니다.

@ConventionBasedComposedContextConfig(locations = "explicitDeclaration")
	static class ConventionBasedComposedContextConfigClass {
	}

 

  • 마커(marker):
    자바에서는 마커 인터페이스처럼, 아무런 메서드도 포함하지 않고 단순히 어떤 클래스나 요소가 특정 특성을 가진다는 것을 표시하기 위해 사용되는 인터페이스나 어노테이션을 말합니다.
    예를 들어, Serializable 인터페이스는 어떤 클래스가 직렬화 가능함을 나타내는 마커 역할을 합니다.
  • 플래그(flag):
    플래그는 코드 내에서 어떤 조건이나 상태를 나타내기 위해 사용되는 신호(일종의 상수 값)입니다.
    어떤 동작을 제어하거나 구분할 때 "이 값이 설정되었으면, ~한 동작을 수행하라"와 같이 조건문에서 활용할 수 있습니다.

여기서 explicitDeclaration는 명시적 선언을 한 상황입니다. 

 

해당 인터페스이스를 보면 

@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ConventionBasedComposedContextConfig {

		// Do NOT use @AliasFor here
		String[] locations() default {};
}

 

 

  • @ContextConfig
    이 어노테이션은 Spring의 컨텍스트 설정과 관련된 어노테이션입니다.
    즉, ConventionBasedComposedContextConfig가 Spring 테스트나 애플리케이션 컨텍스트 구성에 관여함을 암시합니다.
  • @Retention(RetentionPolicy.RUNTIME)
    이 어노테이션은 런타임까지 유지되어, 실행 중 리플렉션(reflection)을 통해 해당 어노테이션 정보를 조회할 수 있게 합니다.
  • 어노테이션 선언 (@interface ConventionBasedComposedContextConfig)
    여기서 새 어노테이션 타입인 ConventionBasedComposedContextConfig를 정의합니다.

이 코드는 Spring 컨텍스트 구성과 관련된 사용자 정의 어노테이션으로, locations라는 속성을 통해 구성 정보를 제공할 수 있도록 설계되었으며, 런타임에도 해당 정보가 유지되어 필요한 시점에 리플렉션으로 접근할 수 있도록 되어 있습니다.

 

여기서 조금더 테스트 코드를 분석해보면 , 어노테이션 속성들을 병합하는 과정입니다, 

즉 해당 테스트코드는 어노테이션 속성들을 병합하는 것을 테스트합니다. 

AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);

 

 

즉 .어노테이션 속성값 오버라이딩 테스트 코드였습니다.!! 

 

 

' > Spring' 카테고리의 다른 글

Async 어노테이션 ,@Async 비동기 처리  (0) 2025.02.19
@Builder? 와 Bulder 패턴?  (0) 2025.02.12
Reflection api GetMethod  (0) 2024.04.20
Spring Data JPA - 구성  (0) 2024.02.25
Spring Data JPA- repository 인터페이스의 정의  (0) 2024.02.18

2020.12.13 - [프로그래밍언어/JAVA] - Thread

 

<JAVA>Thread

Os 수업을 들었다면 알거라고 생각하지만 한번더 정리하는 개념으로 Process 실행중인 프로그램OS로부터 메모리르 할당 받음 Thread실제 프로그램이 수행되는 작업의 최소 단위하나의 프로세스에는

kwaksh2319.tistory.com

스레드 개념을 한번쯤 보면 좋을것같습니다 ㅎ 둘은 좀 다르지만 일단 둘다 비동기 처리입니다.(기초가 중요하다!)

 

@Async 어노테이션은 주로 Spring Framework에서 사용되며, 메서드를 비동기적으로 실행할 수 있도록 해줍니다. 이를 통해 긴 작업이나 I/O 작업 등을 별도의 스레드에서 실행하여, 호출하는 쪽에서는 즉시 다음 작업을 진행할 수 있게 됩니다.

 

비동기 처리 활성화:
@Async를 사용하기 전에, 애플리케이션 설정에 @EnableAsync를 추가하여 비동기 처리를 활성화해야 합니다.

@Configuration
@EnableAsync
public class AsyncConfig {
    // Executor 설정 등 추가 설정 가능
}

 

메서드에 적용:
비동기 처리가 필요한 메서드 위에 @Async 어노테이션을 붙입니다. 이 메서드는 호출한 스레드와 별도로, 스레드 풀에서 실행됩니다.

@Service
public class MyService {

    @Async
    public void executeAsyncTask() {
        // 시간이 오래 걸리는 작업
    }
}

 

반환 타입:

  • void: 결과를 기다리지 않을 경우 사용합니다.
  • Future / CompletableFuture: 비동기 작업의 결과를 받아보고 싶을 때 사용합니다.
@Async
public CompletableFuture<String> asyncMethodWithReturn() {
    // 처리 로직
    return CompletableFuture.completedFuture("완료");
}

 

주의 사항:

  • public 메서드여야 프록시를 통한 AOP 적용이 제대로 이루어집니다.
  • 같은 클래스 내부에서 호출하는 경우에는 프록시가 적용되지 않아 비동기 처리가 동작하지 않을 수 있으므로, 별도의 빈으로 분리하여 호출하는 것이 좋습니다.
  • 스레드 풀 설정을 커스터마이징하여, 애플리케이션에 맞는 스레드 수와 동작 방식을 조정할 수 있습니다.

주의사항

  • 스레드 관리: 비동기 처리 시에는 스레드 풀이나 이벤트 루프 등을 적절하게 설정해, 자원 누수나 과도한 스레드 생성 문제를 피해야 합니다.
  • 동시성 이슈: 여러 스레드에서 동시에 데이터에 접근할 경우 동기화 문제가 발생할 수 있으므로, 상태 관리에 주의해야 합니다.
  • 프레임워크 선택: Java에서는 Netty, Spring WebSocket, Reactor 등 비동기 처리를 지원하는 프레임워크를 활용하면 편리합니다.

간단히 알아봤는데요. 음 뭐 스레드 사용해서 비동기 처리하는 부분인데요. 이론은 알아도 개발하면서 이해하는 것 만큼 좋은것 없는것같습니다. 

다음에 소켓통신을 통해서 개발한 부분을 비동기 처리를 해보도록 하겠습니다. ㅎㅎ ( 실은 회사에서 업무할때 자동화 시스템 추가 개발하는데 async를 써보는데 파일업로드가 비동기 처리가 안되더라고요 , 이유가  파일 업로드의 경우 클라이언트와의 연결이 파일 전송 완료까지 유지되어야 하고, 해당 요청 자체가 블로킹 I/O를 필요로 하므로, 비동기 처리의 효과가 제한이 되더라거요 ㅎ 사실 자주 안써보다보니 이번에 같이 공부하면서 자세히 다뤄보도록 하겠습니다. )

1. Builder 패턴이란?

Builder 패턴은 복잡한 객체의 생성 과정을 단순화하고 가독성을 높이기 위해 사용되는 생성 패턴(Creational Pattern) 중 하나입니다. 특히 매개변수가 많은 클래스를 생성할 때, 생성자 또는 정적 팩토리 메서드보다 더 직관적이고 명확한 방법을 제공합니다.

 

2. Builder 패턴의 장점

  1. 가독성 향상: 어떤 필드가 어떤 값으로 설정되는지 명확하게 보입니다.
  2. 불변성 유지: 객체 생성 후 필드를 변경할 수 없게 하여 불변 객체를 쉽게 만들 수 있습니다.
  3. 유연한 객체 생성: 필수 매개변수와 선택 매개변수를 구분하여 유연하게 객체를 생성할 수 있습니다.
  4. 코드 중복 감소: 여러 생성자 오버로딩 없이 다양한 조합으로 객체를 생성할 수 있습니다.

3. 전통적인 Builder 패턴 예제

public class User {
    private final String firstName;
    private final String lastName;
    private final int age;
    private final String email;

    // private 생성자
    private User(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.email = builder.email;
    }

    // static nested Builder 클래스
    public static class Builder {
        private String firstName;
        private String lastName;
        private int age;
        private String email;

        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        // 최종적으로 객체를 생성하는 build 메서드
        public User build() {
            return new User(this);
        }
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
}

// 객체 생성 예제
public class Main {
    public static void main(String[] args) {
        User user = new User.Builder()
                .firstName("John")
                .lastName("Doe")
                .age(30)
                .email("john.doe@example.com")
                .build();

        System.out.println(user);
    }
}
User{firstName='John', lastName='Doe', age=30, email='john.doe@example.com'}

4. Lombok의 @Builder 어노테이션 사용

Lombok은 자바에서 보일러플레이트 코드를 줄이기 위해 사용하는 라이브러리입니다. @Builder 어노테이션을 사용하면 위와 같은 Builder 패턴을 자동으로 생성해줍니다.

Lombok 사용 예제:

 

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {
    private String firstName;
    private String lastName;
    private int age;
    private String email;
}

public class Main {
    public static void main(String[] args) {
        User user = User.builder()
                .firstName("John")
                .lastName("Doe")
                .age(30)
                .email("john.doe@example.com")
                .build();

        System.out.println(user);
    }
}
User(firstName=John, lastName=Doe, age=30, email=john.doe@example.com)

5. 전통적인 Builder 패턴 vs Lombok @Builder

항목전통적인 Builder 패턴Lombok @Builder 사용

코드 길이 길고 보일러플레이트 코드가 많음 코드가 간결하고 깔끔함
유연성 직접 커스터마이징 가능 기본적인 기능은 자동 생성되나, 커스터마이징은 제한적임
의존성 추가 라이브러리 필요 없음 Lombok 라이브러리 필요
가독성 및 유지보수 가독성이 좋지만 코드가 길어짐 가독성과 유지보수 모두 용이

6. 결론

  • 전통적인 Builder 패턴은 커스터마이징이 필요하거나, 라이브러리 의존성을 피하고 싶은 경우 유용합니다.
  • Lombok의 @Builder는 코드의 가독성과 개발 생산성을 높여주며, 특히 간단한 객체 생성을 빠르게 처리할 수 있습니다.

https://kwaksh2319.tistory.com/1004

웹소켓통신 서버 이용하여 간단한 채팅방 만들기2

https://kwaksh2319.tistory.com/1003 웹소켓통신 서버 이용하여 간단한 채팅방 만들기1구조 : STOMPSTOMP은 **"Simple (or Streaming) Text Oriented Messaging Protocol"**의 약자입니다. 웹소켓(WebSocket)을 사용할 때 메시지를

kwaksh2319.tistory.com


이번에 조금 변경해서 ui/ux 조금 수정을 하고 채팅방까지 구현했습니다. 
그리고 채팅로그가 남도록했습니다. 화면있고 코드도 간단히 설명하겠습니다.
화면: 

메세지를 보내면 저장하도록 변경했습니다. 

 @Override
    public void sendMessage(@DestinationVariable Long roomId, MessageDTO message, StompHeaderAccessor headerAccessor) {
        String clientIp = (String) headerAccessor.getSessionAttributes().get("ipAddress");
        if (clientIp == null) {
            clientIp = "unknown"; // 기본값 설정
        }

        try{
            Optional<Room> room =roomService.findById(roomId);
            if(room.isPresent()){
                Message messageEntity = Message.builder()
                        .room(room.get())
                        .sender(message.getSender())
                        .text(message.getContent())
                        .ip(clientIp)
                        .build();
                messageRepository.save(messageEntity);

                String destination = "/topic/room/" + roomId; // 방 ID 경로

                messagingTemplate.convertAndSend(destination, message); // 해당 방의 사용자들에게 메시지 전송

            } else {
            // 예외를 던지면, 아래 @MessageExceptionHandler가 처리합니다.
            throw new IllegalArgumentException("메세지를 보낼 수 없습니다. 올바른 방이 아닙니다.");
        }

        }catch (Exception e){
            log.error("message table save error");
        }

   }

 
그리고 방을 접속할때 메세지들을 가져옵니다. 

 @Override
    public Page<MessageDTO> findAll(@Param("roomId") Long roomId) {
        int pageSize=100;
        int pageNumber=0;
        Page<MessageDTO> messageDTOPage=null;
        try {

            Page<Message> messages =messageRepository.findAll(roomId,PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").ascending()));

             messageDTOPage = messages.map(message ->
                    MessageDTO.builder()
                            .content(message.getText())
                            .sender(message.getSender())
                            .build());
        }catch (Exception e){
            log.error("message  table get content error or roomid not convert string");
        }

        return messageDTOPage;
    }

 

https://kwaksh2319.tistory.com/1003

웹소켓통신 서버 이용하여 간단한 채팅방 만들기1

구조 : STOMPSTOMP은 **"Simple (or Streaming) Text Oriented Messaging Protocol"**의 약자입니다. 웹소켓(WebSocket)을 사용할 때 메시지를 주고받기 위한 프로토콜 중 하나로, 텍스트 기반의 메시징 프로토콜입니다.

kwaksh2319.tistory.com


 
지난번에 이어서~ 
테이블 설계도 조금 변경했습니다. ( 퇴근 후 개발하는건 늘 쉽지 않네요 ~ , 퇴근후 딴짓을 너무 많이하네요 ㅠ ㅠ )
간단하게 방리스트들을 만들었습니다.
 
화면 : 

1:n 관계로 메세지 테이블을 만들어줬습니다. 

지난번에 소켓통신을 통해서 메세지를 가져왔고요.
이번엔 먼저 간단하게 룸이랑 룸 생성후 리스트만 불러오도록 했습니다.
 
room 

@Entity
@Table(name = "Room")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Room {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ROOM_SEQ")
    @SequenceGenerator(name = "ROOM_SEQ", sequenceName = "ROOM_SEQ", allocationSize = 1)
    @Column(name = "ID")
    private Long id;

    private String roomId;

    private String ip;

    @CreationTimestamp
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Message> messages = new ArrayList<>();

}

 
 jparepository는 간단하게 생략하고 리스트 불러오는 서비스쪽 코드는 

 @Override
    public Page<RoomListDTO> findAll(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        Page<Room> rooms= roomRepository.findAll(pageable);

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        Page<RoomListDTO> roomDTOs=rooms.map(room ->RoomListDTO.builder()
                .id(room.getId())
                .roomId(room.getRoomId())
                .createdAt(room.getCreatedAt().format(formatter))
                .build());
        return roomDTOs;
    }

리스트들을 불러올거고요. 
dto는 

@Getter
@Setter
@Builder
public class RoomListDTO {
    private Long id;
    private String roomId;
    private String createdAt;
}

해당 파라미터들을 가져와서 룸리스트를 보여줄겁니다. 
프론트코드는 ajax를 통해서 가져올거고요.

function getRoomList(){
    var page = 0;
    var size = 10;

    // Ajax 호출
    $.ajax({
        url: '/room/' + page + '/' + size,
        type: 'GET',
        dataType: 'json',
        success: function(response) {
            var rooms = response.content;
            var tbody = $('#roomTable tbody');
            tbody.empty(); 

            if(rooms && rooms.length > 0) {
                $.each(rooms, function(index, room) {
                    var tr = $('<tr></tr>');
                    tr.append('<td>' + room.id + '</td>');
                    tr.append('<td>' + room.roomId + '</td>');
                    tr.append('<td>' + room.createdAt + '</td>');
                    tbody.append(tr);
                });
            } else {
                tbody.append('<tr><td colspan="3">No rooms available.</td></tr>');
            }
        },
        error: function(xhr, status, error) {
            console.error("Ajax 호출 에러: ", error);
        }
    });
}

 
html 내용입니다. 

<h1 style="text-align: center;">Room List </h1>
<table id="roomTable">
    <thead>
    <tr>
        <th>ID</th>
        <th>방 제목</th>
        <th>등록일</th>
    </tr>
    </thead>
    <tbody>
    <!-- 데이터가 여기 채워집니다 -->
    </tbody>
</table>

 
 

구조 :

 

STOMP

STOMP은 **"Simple (or Streaming) Text Oriented Messaging Protocol"**의 약자입니다. 웹소켓(WebSocket)을 사용할 때 메시지를 주고받기 위한 프로토콜 중 하나로, 텍스트 기반의 메시징 프로토콜입니다.
 
구조를 보시면 수신자는 topic or app을 통해서 request channel을 보냅니다. 
아래 소스 코드를 보시면 구성 요소에서  /topic, /app으로 설정되어 있는걸 알수 있습니다. 

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic"); // 메시지 브로커 경로
        config.setApplicationDestinationPrefixes("/app"); // 클라이언트 요청 경로
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat") // WebSocket 엔드포인트
                .addInterceptors(new IpHandshakeHandler())
                .setAllowedOriginPatterns("*")
                .withSockJS(); // SockJS 사용
    }
}

@EnableWebSocketMessageBroker

  • WebSocket 메시지 브로커를 활성화하는 어노테이션입니다.
  • STOMP 프로토콜을 통해 메시지 라우팅과 처리 기능을 제공합니다.

WebSocketMessageBrokerConfigurer

  • WebSocket과 관련된 설정을 커스터마이징할 수 있도록 제공되는 인터페이스입니다.
  • configureMessageBroker와 registerStompEndpoints 메서드를 구현하여 메시지 브로커와 엔드포인트를 설정합니다.

configureMessageBroker(MessageBrokerRegistry config)

메시지 브로커를 설정하는 메서드입니다.

  • config.enableSimpleBroker("/topic")
    • /topic 경로를 사용하는 간단한 메시지 브로커를 활성화합니다.
    • 이 브로커는 구독(Pub/Sub) 모델을 지원하며, 메시지를 특정 대상(destination)으로 전달합니다.
    • 예를 들어, 클라이언트가 /topic/chat을 구독하면 해당 경로로 발행된 메시지를 받을 수 있습니다.
  • config.setApplicationDestinationPrefixes("/app")
    • 클라이언트가 서버로 메시지를 보낼 때 사용할 경로의 접두사를 설정합니다.
    • 클라이언트가 /app/chat으로 메시지를 보내면, 해당 요청은 서버의 컨트롤러에서 처리됩니다.
    • 예를 들어, 서버 측 컨트롤러에서 @MessageMapping("/chat")을 사용해 이 경로를 처리할 수 있습니다.

/topic 을 통해서 채팅 방을 생성합니다. 
관련 roomid가 동일한 구독자들은 그 안에서 메세지를 볼수 이게 됩니다. 
 
메세지를 보내게 되면 /app/chat을 통해서 메세지를 보냅니다. 

백엔드에서 해당 채팅룸의 방이 어디서 보내줘야할지를 보내줍니다.

 

2024.10.09 - [웹/Spring vue 웹 개발] - 소켓 통신 인터페이스 만들기

 

소켓 통신 인터페이스 만들기

요즘 간단하게 주식관련 사이트를 만들어보려고했는데용.흠 친구들도 간간히 주식얘기를 하다보니 퇴근해서 개발을 시작하게 되었습니다. 흠 ... 일단 한국투자증권 open api를 이용을 했고요. 

kwaksh2319.tistory.com

 

 

지난번에 이어서 한투 open api를 통해서 아래 한투 open api

https://apiportal.koreainvestment.com/login

 

 

미국 주식 '애플' 매수 , 매도 데이터를 실시간 소켓통신을 통해서 가져오게했습니다.

이제 사이트에서 데이터를 가져와서 실시간으로 그래프를 그려주고 있습니다.

다음은 간단하게 종목들을 가져오는 것들을 구현할것을 목표로 생각하고 있습니다. 퇴근하고 간간히 하는거라 쉽지 않네요 ㅎ 

 

 

 

 

 

요즘 간단하게 주식관련 사이트를 만들어보려고했는데용.
흠 친구들도 간간히 주식얘기를 하다보니 퇴근해서 개발을 시작하게 되었습니다. 
흠 ...
 
일단 한국투자증권 open api를 이용을 했고요.
 
현재는 대략적으로 실시간으로 소켓통신을 개발을 했습니다. 
머 간략히 얘기해서 소켓통신에 관해서 개발했던게 대학생때 했던거라 기억이 가물가물하기도 해서  간단하게 정리를 해봤습니다. 
대략적인 아이디어는 아래 그림과 같습니다.
 

번호 순대로 일단 가져오고요.
 
예시코드를 간단히 설명하고 끝내겠습니다. 
아래는 코드고 함수 설명은 아래 코드 밑에 작성하겠습니다.
백엔드 코드 : 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

@RestController
public class WebSocketController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @GetMapping("/connect")
    public ResponseEntity<String> connectToWebSocket() {
        try {
            URI uri = new URI("ws://external-websocket-url");

            WebSocketClient webSocketClient = new WebSocketClient(uri) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    System.out.println("WebSocket connected to external server");

                    // WebSocket으로 데이터를 전송
                    send("{\"input\":{\"tr_id\":\"HDFSASP0\",\"tr_key\":\"DNASAAPL\"}}");
                }

                @Override
                public void onMessage(String message) {
                    System.out.println("Received from WebSocket: " + message);  // 받은 데이터 로그 출력

                    // 메시지가 JSON 형식인지 확인
                    if (message.trim().startsWith("{")) {
                        System.out.println("Message is a JSON object");
                    } else {
                        System.out.println("Message is not a JSON object");
                    }

                    // 받은 메시지를 프론트엔드로 전달
                    messagingTemplate.convertAndSend("/topic/stock-data", message);
                }

                @Override
                public void onClose(int code, String reason, boolean remote) {
                    System.out.println("WebSocket connection closed: " + reason);
                }

                @Override
                public void onError(Exception ex) {
                    ex.printStackTrace();
                }
            };

            webSocketClient.connect();

        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<>("Error occurred while connecting to WebSocket", HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return new ResponseEntity<>("WebSocket connection initiated", HttpStatus.OK);
    }
}

 
onOpen은 WebSocket 연결이 성공적으로 이루어졌을 때 호출되는 콜백 함수입니다. WebSocket 클라이언트STOMP 클라이언트에서 WebSocket 핸드셰이크가 성공하고 연결이 열렸을 때, onOpen 함수가 실행됩니다. 이 시점에서 클라이언트는 서버와 WebSocket 통신을 할 준비가 된 상태
 
onMessageWebSocket 서버로부터 메시지를 수신할 때 호출됩니다. WebSocket을 통해 실시간으로 데이터를 주고받는 데 있어 가장 중요한 이벤트 핸들러 중 하나입니다.
(여기 함수를 통해서 받고 아래 코드에 ws url message를 수신합니다.)

messagingTemplate.convertAndSend("/topic/stock-data", message);

 
 
onClose: WebSocket 연결이 닫힐 때 호출.
 
onError: WebSocket 통신 중 오류가 발생할 때 호출.
 
그렇다면 /topic/stock-data는 어디서 url을 맵핑해주는지 알아야겠죠 

WebSocketMessageBrokerConfigurer

클래스를 사용해서 

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    // 클라이언트가 구독할 수 있는 경로 설정
    config.enableSimpleBroker("/topic", "/queue"); // /topic 경로로 구독
    config.setApplicationDestinationPrefixes("/app"); // 클라이언트가 서버에 메시지 전송 시 경로
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    // 클라이언트가 연결할 엔드포인트 설정
    registry.addEndpoint("/ws")
            .setAllowedOriginPatterns("http://localhost:*")  // 포트가 다른 로컬 호스트 허용
            .withSockJS();  // SockJS 사용
}

여기 configure 에 등록해주면 됩니다.
 
프론트 : stompClient에서 커넥트를 하고 subscibe를 통해서 데이터를 받아오면 끝~~ 

const socket = new SockJS('/ws');  // 백엔드에서 설정한 WebSocket 엔드포인트
const stompClient = Stomp.over(socket);

stompClient.connect({}, function (frame) {
    console.log('Connected: ' + frame);

    stompClient.subscribe('/topic/stock-data', function (message) {
        console.log('Received: ', message.body);
    });
}, function (error) {
    console.error('Error in STOMP connection: ', error);
});

 
제가 만든 데이터들을 
가져온 결과물 :

+ Recent posts