
@ExceptionHandler는 Spring MVC 컨트롤러에서 발생하는 예외를 처리하기 위해 사용할 수 있습니다. 예외 발생 시, 사용자에게 적절한 응답을 반환할 수 있도록 도와줍니다. 예제 코드로 살펴보겠습니다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
// 사용자 정의 예외 클래스
class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
// 컨트롤러 클래스
@RestController
public class MyController {
@GetMapping("/resource/{id}")
public String getResource(@PathVariable("id") int id) {
if (id <= 0) {
throw new ResourceNotFoundException("Resource not found with ID: " + id);
}
return "Resource with ID: " + id;
}
// ResourceNotFoundException 처리 메서드
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
// 다른 예외 처리 메서드 추가 가능
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
자바에서는 예외를 처리하기 위해 Exception 클래스 계층을 사용하며, RuntimeException은 그 중 하나입니다. ResourceNotFoundException 클래스에서 super(message)를 호출하였기 때문에 handleResourceNotFoundException 내부에서 ex.getMessage()를 호출하였을 때 "Resource not found with ID: " 문구가 반환될 수 있는 구조입니다.
위에서는 컨트롤러 단위로 예외 처리를 하는 방법을 살펴 보았는데, 컨트롤러가 많아지면 @ExceptionHandler를 반복적으로 사용하는 비효율성이 발생할 수 있습니다. 이때 @RestControllerAdvice를 사용하는 것이 대안이 될 수 있습니다.
@RestControllerAdvice는 Spring MVC에서 예외 처리를 전역적으로 관리할 수 있도록 도와주는 어노테이션입니다.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
// 사용자 정의 예외 클래스
class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
// 컨트롤러 클래스
@RestController
public class MyController {
@GetMapping("/resource/{id}")
public String getResource(@PathVariable("id") int id) {
if (id <= 0) {
throw new ResourceNotFoundException("Resource not found with ID: " + id);
}
return "Resource with ID: " + id;
}
}
// 전역 예외 처리 클래스
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
이전 코드에서는 MyController 내부에 에러 핸들러를 작성하였는데, 이번 코드는 컨트롤러 외부에 GlobalExceptionHandler가 작성되어 있습니다.
전역적으로 처리될만한 에러 케이스는 위 방식으로 작성하면 중복 코드를 줄일 수 있습니다.
RestControllerAdvice 어노테이션 이름에서 유추할 수 있듯이(Advice) 이러한 방식도 AOP의 일종이라고 볼 수 있어 보입니다!