112p ~
@RestControllerAdvice 클래스는 예외와 상태 코드 사이의 매핑을 제공.
카탈로그에 이미 있는 책 생성시 422(처리할 수 없는 개체), 존재하지 않는 책을 가져오려할 때는 404(찾을 수 없음), Book 객체에서 하나 이상의 필드가 잘못되었을 때는 400(잘못된 요청) 응답 반환.
package com.polarbookshop.catalogservice.web;
import com.polarbookshop.catalogservice.domain.BookAlreadyExistsException;
import com.polarbookshop.catalogservice.domain.BookNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
// 클래스가 중앙식 예외 핸들러임을 표시
@RestControllerAdvice
public class BookControllerAdvice {
// 해당 핸들러가 실행되어야 할 대상인 예외 정의
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
String bookNotFoundHandler(BookNotFoundException ex) {
// HTTP 응답 본문에 포함할 메시지
return ex.getMessage();
}
@ExceptionHandler(BookAlreadyExistsException.class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
String bookAlreadyExistsHandler(BookAlreadyExistsException ex) {
return ex.getMessage();
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
var errors = new HashMap<String, String>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
// 빈 메시지 대신 의미 있는 오류 메시지를 위해 유효하지 않은 필드 확인
errors.put(fieldName, errorMessage);
});
return errors;
}
}
다시 빌드, 실행하여 위의 핸들러에 걸리는지 확인해보기.
(윈도우의 명령어는 gradlew.bat bootRun)
http POST :9001/books author="" title="" isbn="0004567891" price=9.55
ㅋㅋㅋㅋ 서버오류가 나버림 400을 바랬거늘 어찌하여.....하고 알게된 사실
@NotBlank → 문자열(String)에만 적용 가능, Double에는 사용 불가능
내가 @NotNull을 @NotBlank로 잘못 썼더라고...? 눈알이 옹이구멍인듯.
package com.polarbookshop.catalogservice.domain;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
// 도메인 객체(Entity) 정의
// 도메인 모델은 불가변 객체인 레코드로 구현
public record Book(
@NotBlank(message = "The book ISBN must be defined")
@Pattern(
regexp = "^[0-9]{10}|[0-9]{13}$",
message = "The ISBN format must be valid"
)
String isbn, // 책 고유번호(식별)
@NotBlank(message = "The book title must be defined")
String title,
@NotBlank(message = "The book author must be defined")
String author,
@NotNull(message = "The book price must be defined")
// @Positive: 널 값이어서는 안되고 0보다 큰 값이어야함.
@Positive(message = "The book price must be greater than zero")
Double price
) { }
빠른 수정 후 재도전.
그리고 잠시 중앙식 예외 핸들러에 대해서 챗지선생님의 정리타임이 있겠습니다....
📌 스프링의 중앙식 예외 핸들러 (Global Exception Handling)란?
스프링에서 중앙식 예외 핸들러는 애플리케이션 전역에서 발생하는 예외를 한 곳에서 처리할 수 있도록 도와주는 기능입니다.
이를 통해 각 컨트롤러에서 개별적으로 예외 처리를 하지 않아도 공통된 로직을 적용할 수 있습니다.
✅ 1. 중앙식 예외 처리의 필요성
보통 컨트롤러에서 예외가 발생하면 이를 try-catch로 개별 처리하는데,
모든 컨트롤러에서 같은 방식으로 예외를 처리하면 코드 중복이 심해지고 유지보수가 어려워집니다.
💡 중앙식 예외 핸들러를 사용하면 모든 컨트롤러에서 발생하는 예외를 한 곳에서 일괄 처리할 수 있습니다.
✅ 2. @ControllerAdvice와 @ExceptionHandler를 사용한 전역 예외 처리
스프링에서는 @ControllerAdvice 와 @ExceptionHandler 를 사용하여 중앙식 예외 처리를 구현할 수 있습니다.
📌 @ControllerAdvice
- 모든 컨트롤러에서 발생하는 예외를 처리할 수 있도록 해주는 전역 예외 처리 클래스
- @ExceptionHandler와 함께 사용하여 특정 예외를 처리함
📌 @ExceptionHandler(Exception.class)
- 특정 예외가 발생했을 때 실행되는 메서드
- 여러 개의 예외를 등록하여 다양한 예외를 처리할 수 있음
✅ 3. 중앙식 예외 핸들러 구현 예제
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
// 예외 처리를 담당하는 클래스
@ControllerAdvice
public class GlobalExceptionHandler {
// 📌 특정 예외 처리 (예: BookNotFoundException)
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
// 📌 모든 예외를 처리하는 기본 핸들러
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return new ResponseEntity<>("서버 오류 발생: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
✅ 이렇게 하면 컨트롤러에서 BookNotFoundException 예외가 발생하면 404 Not Found를 응답으로 보내고,
✅ 그 외 모든 예외는 500 Internal Server Error로 처리됩니다.
✅ 4. @ResponseStatus를 활용한 간단한 예외 처리
스프링에서는 예외 클래스 자체에 HTTP 상태 코드를 부여할 수도 있습니다.
📌 예외 클래스 정의
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND) // 404 반환
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(String message) {
super(message);
}
}
✅ 이렇게 하면 컨트롤러에서 throw new BookNotFoundException("해당 책을 찾을 수 없습니다.");
✅ 예외를 발생시키면 자동으로 404 Not Found 응답이 반환됩니다.
✅ 5. REST API에서 @RestControllerAdvice 사용
REST API에서는 예외 발생 시 JSON 형식으로 응답을 보내는 것이 일반적입니다.
이 경우 **@RestControllerAdvice**를 사용하면 JSON 응답을 쉽게 관리할 수 있습니다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice // REST 컨트롤러 전용 예외 처리
public class GlobalRestExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<ErrorResponse> handleBookNotFoundException(BookNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "서버 오류 발생");
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// JSON 응답용 DTO
class ErrorResponse {
private int status;
private String message;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
// Getter & Setter
public int getStatus() { return status; }
public String getMessage() { return message; }
}
✅ 이렇게 하면 예외 발생 시 JSON 형식으로 상태 코드와 메시지를 반환할 수 있습니다.
✅ 6. 중앙식 예외 처리의 장점
장점설명
코드 중복 감소 | 모든 컨트롤러에서 개별적으로 예외를 처리할 필요 없음 |
유지보수 용이 | 예외 처리를 한 곳에서 관리 가능 |
일관된 응답 형식 유지 | REST API의 경우 JSON 형식으로 일관된 응답 제공 가능 |
보안성 증가 | 내부 서버 오류가 클라이언트에게 노출되지 않도록 관리 가능 |
🚀 정리
- @ControllerAdvice → 모든 컨트롤러에서 발생하는 예외를 한 곳에서 처리 가능
- @ExceptionHandler → 특정 예외를 처리할 메서드 지정
- @RestControllerAdvice → REST API에서 JSON 형태의 예외 응답 반환
- @ResponseStatus → 예외 클래스에 HTTP 상태 코드를 직접 부여 가능
이제 중앙식 예외 처리를 활용하면 프로젝트에서 예외 처리를 더 체계적으로 관리할 수 있습니다! 🚀😊
'Cloud Native Spring in Action' 카테고리의 다른 글
@SpringBootTest 를 통한 통합 테스트 (0) | 2025.02.10 |
---|---|
JUnit5를 이용한 단위 테스트 (0) | 2025.02.07 |
데이터 유효성 검사 (1) | 2025.02.06 |
스프링 MVC를 이용한 REST API 구현, 그리고 Httpie 설치와의 사투 (0) | 2025.02.06 |
스프링 MVC를 이용한 RESTful 애플리케이션 구축(2) (1) | 2025.02.05 |