* 참고

일반 예외로 선언할 경우 Exception을 상속받아 구현 (컴파일러가 체크)

실행 예외로 선언할 경우에는 RuntimeException을 상속받아 구현


1. CustomException을 사용하는 이유

상세한 예외 정보를 제공, 이름으로도 정보 전달이 가능

Enum과 함께 사용하여 예외에 대한 응집도를 높일 수 있음

@ControllerAdvice, @RestControllerAdvice에서 해당 Custom Exception에 대한 자세한 후처리가 가능 (전역적인 예외 처리)

 

2. 총 4가지의 클래스

1. Exception이 발생하였을때 알려준 Status 상태와 메시지를 담은 enum 클래스

package org.example.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum CustomErrorCode {

    //== 400 ==//
    NOT_SUPPORTED_HTTP_METHOD(HttpStatus.BAD_REQUEST, "지원하지 않는 Http Method 방식입니다."),
    NOT_VALID_METHOD_ARGUMENT(HttpStatus.BAD_REQUEST, "유효하지 않은 Request Body 혹은 Argument입니다."),


    BAD_REQUEST(HttpStatus.BAD_REQUEST, "ᕕ( ᐛ )ᕗ 잘못된 요청입니다."),
    NOT_FOUND(HttpStatus.NOT_FOUND, "ᕕ( ᐛ )ᕗ 리소스를 찾을 수 없습니다."),


    //== 500 ==//
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다.");

    private final HttpStatus httpStatus;
    private final String message;

}

2. Dto 역할을 하고있는 CustomErrorResponse

package org.example.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

public record ResponseMessage <T> (HttpStatus status, String message, T data) {

    public static ResponseEntity<ResponseMessage> error(CustomException e) {
        return ResponseEntity
                .status(e.getCustomErrorCode().getHttpStatus())
                .body(new ResponseMessage<>(
                        e.getCustomErrorCode().getHttpStatus(),
                        e.getCustomErrorCode().getMessage(),
                        null
                ));
    }

    public static <T> ResponseEntity<ResponseMessage> success(HttpStatus status, String message, T data) {
        return ResponseEntity
                .status(status)
                .body(new ResponseMessage<>(
                        status,
                        message,
                        data
                ));
    }
}

3. RuntimeException을 상속받아 커스텀화 해준 클래스

package org.example.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class CustomException extends RuntimeException {
    private final CustomErrorCode customErrorCode;

}

4. 커스텀화 한 클래스 및 다른 Exception들의 Handler 클래스

package org.example.exception;

import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice // CustomException 발생 시, 프런트로 보낼 ErrorDTO 를 생성하고, ErrorDTO 를 전달하는 handler
public class CustomExceptionHandler {

    @ExceptionHandler(value = CustomException.class)
    public ResponseEntity<ResponseMessage> handleCustomException(CustomException e) {
        // 에러에 대한 후처리
        log.error("[handleCustomException] {} : {}", e.getCustomErrorCode().name(), e.getCustomErrorCode().getMessage());
        return ResponseMessage.error(e);
    }

    // HttpRequestMethodNotSupportedException 처리
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<ResponseMessage> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e) {
        log.error("[handleHttpRequestMethodNotSupported] {}", e.getMessage());
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ResponseMessage<>(
                        HttpStatus.BAD_REQUEST,
                        CustomErrorCode.NOT_SUPPORTED_HTTP_METHOD.getMessage(),
                        null
                ));
    }

    // MethodArgumentNotValidException 처리
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<ResponseMessage> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
        log.error("[handleMethodArgumentNotValid] {}", e.getMessage());
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ResponseMessage<>(
                        HttpStatus.BAD_REQUEST,
                        CustomErrorCode.NOT_VALID_METHOD_ARGUMENT.getMessage(),
                        null
                ));
    }

    // 전역 예외 처리 (기타)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ResponseMessage> handleGeneralException(Exception e) {
        log.error("[handleGeneralException] {}", e.getMessage(), e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ResponseMessage<>(
                        HttpStatus.INTERNAL_SERVER_ERROR,
                        CustomErrorCode.INTERNAL_SERVER_ERROR.getMessage(),
                        null
                ));
    }
}

 

 

참고 : 

https://mangkyu.tistory.com/204

https://veneas.tistory.com/entry/Java-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%98%88%EC%99%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0Custom-Exception

'Spring' 카테고리의 다른 글

필터와 인터셉터  (0) 2025.02.07
ArgumentResolver  (0) 2025.01.19
디버깅 모드  (0) 2025.01.19
FeignClient  (1) 2025.01.19
멀티 모듈 (Multi-Module)  (0) 2025.01.17

1. 필터 

- 스프링 외부의 서블릿에서 제공하는 공통처리 기능

- 스프링 내로 요청이 들어오기 전과 스프링의 요청이 나갈 때 처리 가능 

- 조금 더 low level 처리 가능

 

1-2. 필터 흐름 : HTTP 요청 > WAS > 필터 > 서블릿(스프링의 디스패쳐 서블릿) > 컨트롤러

- 적절하지 않은 요청인 경우 필터는 서블릿 호츨 X

 

1-3. 

필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고 관리

- init() : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출

- doFilter() : 고객의 요청이 올 때마다 해당 메서드 호출, 필터의 로직을 구현하면 된다

- destroy() : 필터 종료 메서드. 서블릿 컨테이너가 종료될 때 호출 

 

2. 인터셉터

- 스프링MVC 에서 제공하는 공통처리 기능 

- 실제 매핑된 handler 정보 확인 가능 (어떤 것이 실제 내 요청 처리하는지 확인 가능)

- 조금 더 상세한 조건식과 세부적인 스펙(post, pre, after) 를 통해 구체적인 시점에 구체적인 동작 가능

- AOP 와 비교한다면 AOP 는 인터셉터보다 더 구체적인 조건 (어노테이션, 파라미터, 주소 등) 과 동작 위치 (afterThrowing 등) 을 갖음

 

2-1. 인터셉터 흐름 : HTTP 요청 > WAS > 필터 > 서블릿 > 스프링 인터셉터 > 컨트롤러

- 스프링 인터셉터는 디스패쳐 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출

- 스프링 MVC가 제공하는 기능이기 때문에 결국 디스패쳐 서블릿 이후에 등장

(스프링 MVC 의 시작점 = 디스패쳐 서블릿)

- 인터셉터의 URL 패턴은 서블릿의 URL 패턴과 달리 매우 정밀함

- 적절하지 않은 요청인 경우 인터셉터는 컨트롤러 호출 X

예시. 인터셉터 체인

ex. 로그를 남기는 인터셉터 1 > 로그인 여부를 체크하는 인터셉터 2 

 

2-2. HandlerInterceptor 를 구현 (implements)

- 호출 전 / preHandle() :

- 호출 후 / postHandle() : 컨트롤러 예외 발생시 호출 X

- 요청 완료 이후 / afterHandle() : 항상 호출, 예외랑 무관하게 공통처리시 사용

 

더보기

* 서블릿 필터의 경우 단순하게 doFilter() 하나만 제공

* 서블릿 필터의 경우 단순히 request, response 만 제공했지만,

인터셉터는 어떤 컨트롤러(handler)가 호출되는지 , 어떤 (modelAndView) 가 변환되는지 응답 정보도 받을 수 있다.

 

참고 : https://mangkyu.tistory.com/173

'Spring' 카테고리의 다른 글

CustomException  (0) 2025.02.07
ArgumentResolver  (0) 2025.01.19
디버깅 모드  (0) 2025.01.19
FeignClient  (1) 2025.01.19
멀티 모듈 (Multi-Module)  (0) 2025.01.17

1. 2PL (Two-Phase Locking) - 2단계 잠금

목적 : 트랜잭션의 일관성직렬 가능성(Serializability) 보장

동작방식 : 트랜젝션이 자원(데이터)에 접근할때 락을 걸고 트랜젝션이 완료될 때까지 락을 유지 

단계 락 획득(확장) → 락 해제(축소)

장점:

직렬 가능성 보장: 트랜잭션 간의 충돌을 방지하여 데이터 일관성 유지

단점:

교착 상태(Deadlock) 발생 가능성: 트랜잭션이 서로의 락을 기다리며 멈추는 상황 발생

성능 저하: 락으로 인한 병목 현상

 

2. 2PC (Two-Phase Commit) - 2단계 커밋

목적 : 분산 트랜잭션 환경에서 원자성(Atomicity) 보장

여러 노드(데이터베이스) 간의 트랜잭션이 모두 성공하거나 모두 실패하도록 하는 프로토콜

동작방식 : Coordinator(조정자)와 여러 Participant(참여자) 간의 두 단계 절차로 진행

단계 준비(Prepare) → 커밋/롤백(Commit/Rollback)

장점:

원자성 보장: 모든 노드가 동일한 상태로 유지됨

데이터 일관성 유지: 분산 환경에서도 데이터 불일치 방지

 

단점:

Coordinator 장애 시 문제 발생: 장애 복구가 복잡

성능 저하: 네트워크 지연, 트랜잭션 대기 시간 증가

더보기

2PL은 데이터베이스 트랜잭션의 동시성 제어에 초점을 맞춘 기술

주로 단일 데이터베이스 환경에서 사용

2PC분산 환경에서 트랜잭션의 원자성을 보장하는 프로토콜

여러 데이터베이스 간의 트랜잭션 일관성을 유지할 때 사용

 

둘 다 데이터 일관성을 유지하기 위한 방법이지만, 적용되는 환경과 중점이 다름 

'Database' 카테고리의 다른 글

CQRS 패턴  (0) 2025.02.05
더보기

command (write) > 마스터만 보게 ,

쿼리 (조회) > 슬레이브만 보게

 

"프로젝트의 패키지 구조 및 컨벤션"

ex . PaymentService = paymentCommandService + paymentQueryService

DB > M / S Application > CQRS

 

부하를 줄여준다

1. CQRS패턴이란 ?

명령(Command)과 질의(Query)의 책임(Responsebilitiy)을 분리(Segregation)하여 시스템의 복잡성을 관리하는 아키텍쳐 패턴

쓰기를 위한 데이터 모델(Write Model)과 읽기를 위한 데이터 모델(Read Model)을 분리하는 패턴

복잡한 도메인 로직과 읽기/쓰기 요구사항이 뚜렷하게 다른 시스템에서 유용

 

1-1. 명령 모델(Command Model)

- 데이터를 변경하는 작업(쓰기 작업)을 처리

- 특정 명령을 받아서 비즈니스 로직과 유효성 검사를 수행하고, 데이터를 변경

- 일반적으로 서비스 로직과 관련이 있고, 데이터 생성, 수정, 삭제와 같은 작업을 담당

1-2. 쿼리 모델(Query Model)

- 데이터를 조회하는 작업(읽기 작업)을 처리

- 최적화된 읽기 작업을 위해 종종 데이터를 별도의 형태로 저장

- 데이터베이스나 다른 저장소에서 데이터를 읽고 결과를 반환하는 역할을 담당

 

2. 적용 방법

- 1단계 : 단일 Data Store 에 단일 어플리케이션 내에서 분리된 계층으로 나누는 방식

- 2단계 : 다중 DB , Broker를 통해서 이중화 된 데이터베이스를 동기화

- 3단계 : 이벤트 소싱(Event Sourcing) 패턴  (어플리케이션 내의 모든 처리 내용을 이벤트로 전환해서 이벤트 스트림을 별도의 DB에 저장하는 방식)

 

 

3. 장점과 단점

3-1. 장점 

1. 확장성 (Scalability)

읽기 쓰기 분리로 각각 독립적으로 확장 가능

읽기 요청이 많은 시스템에서는 조회 부분만 별도로 최적화하거나 확장 가능 

2. 성능 최적화 (Performance Optimization) 

읽기 모델과 쓰기 모델을 각각 최적화 할 수 있음 

예를 들어 조회 쿼리는 캐싱(Redis) , 비정규화된 데이터 구조 등을 활용하여 빠르게 처리 가능 

3. 복잡성 관리(Separation of Concerns)

비즈니스 로직(쓰기)와 조회 로직을 분리하여 코드가 명확하고 유지보수가 쉬워짐 

4. 유연한 데이터 모델링

읽기와 쓰기에 서로 다른 데이터 모델을 사용할 수 있어 각 요구사항에 맞는 데이터 구조 설계가 가능 

5. 이벤트 소싱(Event Sourcing)과의 시너지  (이건 잘 이해가 안간다 ) 

데이터 변경 이력을 그대로 저장하고 재구성 가능 

감사추적 (Audit Trail) 이나 복구 기능에 유리 

6. 보안 강화

쓰기와 읽기 모델이 분리되므로 권한 제어를 더 정교하게 설정 가능 

예: 읽기 전용 API와 쓰기 전용 API 분리

 

3-2. 단점 

1. 복잡성 증가

읽기/쓰기 모델 간의 동기화 관리가 필요

2. 데이터 일관성 문제 

읽기 모델과 쓰기 모델 간의 최종 일관성(Eventual Consistency) 문제가 발생 가능 

실시간으로 데이터를 반영해야 하는 시스템에는 부적합할 수 있다 

3. 개발 및 유지보수 비용 증가

초기 개발 비용과 유지보수 비용이 증가

배포, 테스트, 디버깅 과정이 더 복잡해질 수도 있다 

4. 이벤트 관리의 어려움 

이벤트 소싱과 함께 사용할 경우, 이벤트 버전 관리나 이벤트 스토어 관리가 까다로울 수 있음

5. 적용이 불필요할 수도 있음

단순한 CRUD 기반 애플리케이션에서는 오히려 과한 설계가 될 수 있으며, 단순한 요구사항에 CQRS를 적용하면 오히려 개발 속도 저하로 이어질 수 있음

 

* CQRS가 적합한 경우

• 복잡한 도메인 로직을 다루는 시스템 (ex. 금융 거래 시스템, 전자상거래 플랫폼)

• 읽기/쓰기 비율이 불균형한 시스템 (ex. 읽기 요청이 월등히 많은 경우)

• 확장성과 성능이 중요한 시스템

• 이벤트 소싱을 필요로 하는 시스템

 

* CQRS가 불필요한 경우

• 단순한 CRUD 애플리케이션

• 작은 팀이나 짧은 개발 주기를 가진 프로젝트

• 실시간 강력한 일관성이 필요한 시스템

 

예시  

프로젝트 구조 

src/main/java/com/example/cqrs/
│
├── command/
│   ├── controller/
│   │   └── ProductCommandController.java
│   ├── service/
│   │   └── ProductCommandService.java
│   └── model/
│       └── Product.java
│
├── query/
│   ├── controller/
│   │   └── ProductQueryController.java
│   ├── service/
│   │   └── ProductQueryService.java
│   └── dto/
│       └── ProductDTO.java
└── repository/
    └── ProductRepository.java

	•	command/: 쓰기(등록, 수정, 삭제) 책임
	•	query/: 읽기(조회) 책임
	•	repository/: 데이터 저장소 (공통)

 

참고

https://terrys-tech-log.tisory.com/48

https://f-lab.kr/insight/understanding-cqrs

 

'Database' 카테고리의 다른 글

2PL (Two-Phase Locking) vs 2PC (Two-Phase Commit)  (0) 2025.02.05

1. 애매모호한 트랜잭션의 개념

1-1. ACID의 의미

트랜잭션이 제공하는 안전성 보장(ACID) : 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability) 

원자성(Atomicity) : 여러 쓰기 작업이 하나의 원자적인 트랜잭션으로 묶이고 트랜잭션은 완료(커밋)되거나 결함 발생 시 해당 트랜잭션의 모든 쓰기 작업이중단(어보트)되거나 둘 중 하나여야 한다.

일관성(Consistency)  : ACID에서의 일관성은 애플리케이션의 불변식 개념에 의존하고, 일관성을 유지하도록 트랜잭션을 올바르게 정의하는 것은 애플리케이션의 책임이다.

격리성(Isolation) : 동시에 실행되는 트랜잭션은 서로 격리된다. 오라클의 직렬성 격리는 스냅샷 격리를 구현한 것

지속성(Durability) : 트랜잭션이 성공적으로 커밋됐다면 트랜잭션에서 기록한 모든 데이터는 손실되지 않는다는 보장

1-2. 단일 객체 연산과 다중 객체 연산

다중 객체 트랜잭션은 읽기 및 쓰기 연산들이 동일한 트랜잭션에 속하는지 알아낼 수단이 있어야 한다. 

관계형 데이터베이스에서는 TCP 연결 기반으로 BEGIN TRANSACTION문, COMMIT문 사이 모든것은 같은 트랜잭션에 속하는 것으로 여겨짐

다중 객체 트랜잭션의 필요성 

2. 완화된 격리 수준

2-1. 커밋 후 읽기

가장 기본적인 트랜잭션 격리로 두 가지를 보장해준다

  1. 데이터베이스에서 읽을 때 커밋된 데이터만 본게 된다 (더티 읽기가 없음)
  2. 데이터베이스에 쓸 때 커밋된 데이터만 덮어쓰게 된다 (더티 쓰기가 없음)

2-2. 스냅샷 격리와 반복 읽기

트랜잭션은 특정 시점에 고정된 데이터베이스의 일관된 스냅샷만을 볼 수 있다

스냅샷 격리 구현은 커밋 후 읽기 격리처럼 전형적으로 더티쓰기를 방지하기 위해 쓰기 잠금을 사용한다

스냅샷 격리의 핵심 원리는 읽는 쪽에서 쓰는 쪽을 결코 차단히지 않고 쓰는 쪽에서 읽은 쪽을 결코 차단하지 않는다는 것 

다중 버전 동시성 제어(multi-version concurreny control, MVCC) : 데이터베이스는 객체마다 커밋된 버전 여러 개를 유지할 수 있어야 한다

반복 읽기(repeatable read) = 스냅샷 격리

2-3. 갱신 손실 방지

갱신 손실(lost update)애플리케이션이 데이터베이스에서 값을 읽고 변경한 후 변경된 값을 다시 쓸 때 발생 가능

두 트랜젝션이 이 작없을 동시에 하면 두 번째 쓰기 작업이 첫 번째 변경을 포함하지 않으므로 변경중 하나는 손실 될 수 있다

해결책

1. 원자적 쓰기 연산

2. 명시적인 잠금

3. 갱신 손실 자동 감지

4. compare-and-set

5. 충돌 해소와 복제

2-4. 쓰기 스큐와 팬텀

쓰기 스큐(write skew) : 두 트랜잭션이 동시에 같은 객체를 읽어서 두 개의 다른 객체를 갱신, 더티쓰기나 갱신 손실 이상 현상 발생

3. 직렬성(Serializability)

직렬성 격리는 여러 트랜잭션이 병렬로 실행되더라도 최종결과는 동시성 없이 한 번에 하나씩 직렬로 실행될 때와 같도록 보장

3-1. 실제적인 직렬 실행

동시성 문제를 해결하는 가장 간단한 방법은 동시성을 완전히 제거하는 것, 한 번에 트랜잭션 하나씩만 직렬로 단일 스레드에서 실행 

단일 스레드를 활용하기 위해서는 트랜잭션이 전통적인 현태와는 다르게 구조화 되어야 한다

현대의 스토어드 프로시저(stored procedure) 구현은 PL/SQL을 버리고 기존의 범용 프로그래밍 언어를 사용한다

단일 스레드 시스템의 처리량을 높이기 위해 파티셔닝 기법을 활용하면 된다

3-2. 2단계 잠금(2PL, 2-phase locking)

2단계 잠금(2PL)과 2단계 커밋2PC(2PC)는 아주 비슷하게 들리지만 완전히 다르다

2PL은 쓰기 트랜잭션이 다른 쓰기 트랜잭션 뿐만아니라 읽기 트랜잭션도 진행하지 못하게 막는다

2단계 잠금의 가장 큰 약점은 성능이다. 완화된 격리 수준을 쓸 때보다 트랜잭션 처리량과 질의 응답 시간이 크게 나빠진다

2단계 잠금이 서술 잠금 까지 포함하면 모든 형태의 쓰기 스큐를 막을 수 있고 격리 수준이 직렬성 격리가 된다

서술 잠금은 성능이 좋지 않아 대부분 2PL을 지원하는 데이터베이스는 서술 잠금을 간략하게 근사한 색인 범위 잠금, 다음 키 잠금을 구현

색인 범위 잠금 서술 잠금보다 정밀하지 않지만 오버헤드가 훨씬 더 낮기 때문에 좋은 타협안이 된다

3-3. 직렬성 스냅샷 격리(SSI, Serializable Snapshot Isolation)

직렬성 스냅샷 격리는 낙관적 동시성 제어 기법이다. 

예비 용량이 충분하고 트랜잭션 사이의 경쟁이 너무 심하지 않으면 낙관적 동시성 제어 기법은 비관적 동시성 제어보다 성능이 좋은 경향이 있다

SSI는 스냅샷 격리 위에 쓰기 작업 사이의 직렬성 충돌을 감지하고 어보트시킬 트랜잭션을 결정하는 알고리즘을 추가한다 

2단계 잠금과 비교할 때 트랜잭션이 다른 트랜잭션을 기다리느라 차단되지 않기 때문에 읽기 작업이 많은 경우 성능이 더 뛰어나다

순차 실행과 비교할 때 단일 CPU 코어의 처리량에 제한되지 않는다

어보트 비율은 전체적인 성능에 큰 영향을 미친다

오랫동안 데이터를 읽고 쓰는 트랜잭션은 충돌 가능성이 높아 어보트되기 쉬우므로 읽기-쓰기 트랜잭션이 짧을 수록 유리

 

참고

'Books' 카테고리의 다른 글

[대규모 중심 어플케이션 설계] 05장. 복제  (0) 2025.01.15

try-with-resource는 Java 7에서 도입된 기능

리소스를 명시적으로 닫을 필요 없이 자동으로 닫아주는 기능을 제공

 

1. Try-with-Resource란?

java.lang.AutoCloseable 인터페이스를 구현한 리소스에 대해 사용할 수 있는 문법
try 블록이 끝나면 자동으로 해당 리소스의 close() 메서드를 호출
리소스를 명시적으로 닫지 않아도 되므로 코드가 간결하고 안전하며, 예외 처리를 보다 쉽게 관리가능

 

2. 기존 방식과의 비교

2-1. Java7 이전의 try-catch-finally

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("example.txt"));
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

2-2. Try-with-Resource 방식

try (ResourceType resource = new ResourceType()) {
    // 리소스 사용
} catch (ExceptionType e) {
    // 예외 처리
}

 

3. AutoCloseable과 Closeable의 차이

AutoCloseable:
Java 7에서 도입된 인터페이스, try-with-resource에서 사용
close() 메서드는 checked exception 또는 unchecked exception

Closeable:
Java 5에서 도입된 인터페이스, java.io 패키지의 클래스에서 주로 사용
close() 메서드는 반드시 IOException


AutoCloseable은 Closeable보다 더 일반화된 인터페이스, 
예외 처리 방식에 유연성을 제공

 

5. 중첩 리소스 처리

try-with-resource를 사용하면 여러 리소스를 동시에 처리가능

try (
    BufferedReader br = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
    }
} catch (IOException e) {
    e.printStackTrace();
}

리소스를 여러 개 선언하면, 선언된 순서와 반대로 닫힌다

 

6. 장점

1. 리소스 누수 방지:
명시적으로 close()를 호출할 필요가 없으므로 실수로 리소스를 닫지 않는 문제를 방지

2. 가독성 향상:
코드가 간결하고 명확

3. 안전한 예외 처리:
리소스를 닫는 과정에서 발생하는 예외를 별도로 처리 X

4. 모든 에러에 대한 스택 트레이스 가능

 

7. 주의사항

1. try-with-resource는 선언된 리소스가 반드시 AutoCloseable을 구현
2. 리소스를 블록 외부에서 사용하려면 try-with-resource를 사용 불가
3. 예외가 발생하면 close() 메서드에서 던진 예외와 원래의 예외가 동시에 발생할 수 있으므로, 
getSuppressed() 메서드로 추가 정보를 확인 필요

try (MyResource resource = new MyResource()) {
    throw new RuntimeException("예외 발생");
} catch (Exception e) {
    for (Throwable suppressed : e.getSuppressed()) {
        System.out.println("Suppressed: " + suppressed);
    }
}

 

 

참고 : 

https://mangkyu.tistory.com/217

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-Try-With-Resource-%EB%AC%B8%EB%B2%95

'Java' 카테고리의 다른 글

불변 객체(Immutable Object)  (0) 2025.01.18
Record Class (불변 데이터 클래스)  (1) 2025.01.17
제네릭(Generic)  (0) 2025.01.17

1. 핵심 개념

 Spring MVC에서 HTTP 요청을 처리할 때, 요청의 파라미터를 메서드 인자로 변환하는 역할
 일반적으로 컨트롤러 메서드에서 요청 파라미터를 처리하기 위해 사용
 HandlerMethodArgumentResolver를 확장하여 구현하며, 이를 통해 커스텀 인자 변환을 처리

* HandlerMethodArgumentResolver: 
Spring MVC의 @RequestMapping 메서드나 기타 핸들러 메서드의 매개변수를 자동으로 변환하는 인터페이스

2. 동작 원리

더보기

사용자가 웹 브라우저를 통해 요청하면 DispatcherServlet이 이를 받음

> DispatcherServlet은 해당 요청에 맞는 URI를 HandlerMapping에서 검색

> 이 때, RequestMapping으로 구현한 API를 찾게 되는데, 이들은 RequestMappingHandlerAdapter가 모두 가지고 있음

> 원하는 Mapping을 찾은 경우, 첫 번째로 Intercepter를 처리

> Argument Resolver 처리

> Message Converter 처리

> Controller Method Invoke

1.	클라이언트 요청: 
	클라이언트가 HTTP 요청을 보내면, Spring MVC는 요청 파라미터를 컨트롤러 메서드의 인자로 변환준비
2.	ArgumentResolver의 역할: 
	컨트롤러 메서드의 매개변수에 맞는 값을 찾고, 이를 해당 타입으로 변환
    예를 들어, @RequestParam이나 @PathVariable로 요청 파라미터를 전달받는 것처럼, 
    ArgumentResolver는 파라미터에 적합한 값을 동적으로 설정
3.	컨트롤러 메서드 실행: 
	매개변수가 해결되면, Spring MVC는 이를 컨트롤러 메서드에 전달하고, 해당 메서드를 실행

3. 핸들러 메서드 인자 처리 방식

기본적으로 Spring은 여러 종류의 인자 변환을 지원하지만, 
특정 요구 사항에 맞는 사용자 정의 변환이 필요하다면 커스텀 ArgumentResolver를 구현하여 사용

* (참고)기본 ArgumentResolver

	•	@RequestParam: URL 쿼리 파라미터를 메서드 인자로 변환
	•	@PathVariable: URI 경로에서 변수를 추출하여 메서드 인자로 변환
	•	@RequestBody: HTTP 요청 본문을 객체로 변환 (예: JSON을 POJO로 변환)
	•	@ModelAttribute: 모델 객체를 생성하여 요청 데이터를 바인딩

4. 커스텀 ArgumentResolver 구현 예

@Component
public class CustomArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 특정 파라미터 타입에 대해서만 이 리졸버가 동작하도록 설정
        return parameter.getParameterType().equals(Long.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // HTTP 요청에서 헤더 "X-User-Id"를 가져와 Long 타입으로 반환
        String userId = webRequest.getHeader("X-User-Id");
        return userId != null ? Long.parseLong(userId) : null;
    }
}

5. ArgumentResolver 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private CustomArgumentResolver customArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(customArgumentResolver);  // 커스텀 ArgumentResolver 등록
    }
}

6. 사용 예시

@RestController
public class UserController {

    @GetMapping("/user")
    public String getUser(@RequestHeader("X-User-Id") Long userId) {
        return "User ID: " + userId;
    }
}

7. 장점과 활용

유연성: 
특정 데이터 변환 규칙에 맞게 요청 데이터를 처리할 수 있어 유연성이 높아짐

재사용성: 
복잡한 파라미터 변환 로직을 여러 컨트롤러에서 재사용 가능

가독성: 
코드의 가독성을 높이고, 중복을 줄이는 데 유리

 

 

참고 : 

더보기

* Interceptor와 ArgumentResolver의 차이
ArgumentResolver는 인터셉터 이후에 동작을 하며, 
어떠한 요청이 컨트롤러에 들어왔을 때, 
요청에 들어온 값으로부터 원하는 객체를 반환하기위해 사용한다. 
반면에 인터셉터는 실제 컨트롤러가 실행되기 전에 요청을 가로채며 특정 객체를 반환할 수 없고 
오직 boolean 혹은 void 반환 타입만 존재한다. 
하지만 파라미터로 받는 객체의 값을 변환하여 컨트롤러에 데이터를 전달할 수는 있다.
 
Request로부터 특정 객체를 추출하는 작업은 ArgumentResolver에서 수행하고
Interceptor는 인증/인가, 로깅 등의 본인만이 할 수 있는 작업을 진행하는 것이 가장 좋다

https://seongwon.dev/Spring-MVC/20220629-ArgumentResolver%EB%9E%80/

'Spring' 카테고리의 다른 글

CustomException  (0) 2025.02.07
필터와 인터셉터  (0) 2025.02.07
디버깅 모드  (0) 2025.01.19
FeignClient  (1) 2025.01.19
멀티 모듈 (Multi-Module)  (0) 2025.01.17

주요 디버깅명령어

 

1. Step Over

기능: 
현재 break 된 파일에서 다음 라인 이동 
break 걸린 라인을 전부 실행 후, 다음라인으로 이동

현재 실행 중인 라인의 코드가 함수 호출을 포함할 경우, 
해당 함수의 내부로 들어가지 않고 함수 호출을 “건너뛰어서” 실행 
함수가 호출되면 함수가 끝날 때까지 기다리지 않고, 그 다음 코드 라인으로 넘어감

사용 예: 
함수 내부를 디버깅할 필요가 없을 때 사용
코드 흐름을 빠르게 따라가되, 내부 동작을 신경 쓰지 않으려는 경우 유용

2. Step Into

기능:
현재 break 된 라인에서 실행하고 있는 라인으로 이동
 
현재 실행 중인 코드 라인이 함수 호출을 포함할 경우, 
그 함수 내부로 들어가서 함수 내에서 실행되는 각 라인도 추적
즉, 함수의 내부를 디버깅할 때 사용

사용 예: 
함수 내부에서 문제가 발생하는지 확인하고 싶을 때 사용
함수 내부 로직까지 상세히 살펴보고 싶을 때 유용

3. Resume Program

기능:  
다음 break point로 이동
첫번째 break point에서 다음 break point로 이동

 

참고 : 

https://jojoldu.tistory.com/149

 

'Spring' 카테고리의 다른 글

필터와 인터셉터  (0) 2025.02.07
ArgumentResolver  (0) 2025.01.19
FeignClient  (1) 2025.01.19
멀티 모듈 (Multi-Module)  (0) 2025.01.17
API(application programming interface), RestAPI  (0) 2023.05.22