해당 메서드 코드 :

 

테스트 코드 통해서 분석 

@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을 통해서 메세지를 보냅니다. 

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

 

https://openssl-library.org/

 

OpenSSL Library

What and Who The OpenSSL software library is a robust, commercial-grade, full-featured toolkit for general-purpose cryptography and secure communication. It is developed under the OpenSSL Mission with support from the OpenSSL Foundation and OpenSSL Corpora

openssl-library.org

다운로드 

 

os: 리눅스  (우분트) 

open ssl 설치

sudo apt update
sudo apt install openssl libssl-dev

 

키 생성 후 암호 입력 

openssl genrsa -aes256 -out private_key.pem 2048

csr 생성 

openssl req -new -key private_key.pem -out request.csr

csr 정보 입력후  완료

Country Name (2 letter code) [AU]: KR
State or Province Name (full name) [Some-State]: Seoul
Locality Name (eg, city) []: Seoul
Organization Name (eg, company) [Internet Widgits Pty Ltd]: My Company Inc.
Organizational Unit Name (eg, section) []: IT Department
Common Name (e.g. server FQDN or YOUR name) []: www.example.com
Email Address []: admin@example.com

A challenge password []: 
An optional company name []:

 

Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "start_value" not found 

 

spring.jpa.hibernate.ddl-auto=update

를 update 에서 create로 

spring.jpa.hibernate.ddl-auto=create

 

대부분은 기존 테이블/시퀀스가 꼬인 경우여서 그런거여서 새롭게 다시 만들어주면 해결됩니다. 

+ Recent posts