@ExceptionHandler

@ExceptionHandler는 특정 컨트롤러 내에서 발생할 수 있는 예외를 처리하기 위한 메서드에 적용되는 어노테이션이다.

이 어노테이션이 적용된 메서드는 해당 컨트롤러에서 처리되지 않은 예외를 캐치하고, 그 예외에 대한 사용자 정의 처리 로직을 실행한다.

메서드는 예외 객체를 파라미터로 받을 수 있으며, 적절한 응답을 반환할 수 있다.

활용

ProductController 클래스는 /products 엔드포인트로 들어오는 GET 요청을 처리한다. 요청 시 keyword 파라미터를 받고, 강제로 IllegalArgumentException 예외를 발생시키도록 설정되어 있다. 예외가 발생하면, handleIllegalArgumentException 메서드가 이를 처리하여 HTTP 상태 코드 400 (Bad Request)과 함께 "IllegalArgumentException occurred" 메시지를 반환한다.

@RestController
public class ProductController {

    @GetMapping("/products")
    public ResponseEntity<Void> searchProduct(@RequestParam String keyword) {
        if (true) {
            throw new IllegalArgumentException("Invalid keyword: " + keyword);
        }

        return ResponseEntity.ok().build();
    }

    @ExceptionHandler(value = IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("IllegalArgumentException occurred", HttpStatus.BAD_REQUEST);
    }
}

아래 테스트 코드에서는 RestAssured를 사용하여 /products?keyword=apple 요청을 보낸 후, 응답의 상태 코드를 확인한다. IllegalArgumentException이 발생할 때, 올바르게 HTTP 상태 코드 400 (Bad Request)이 반환되는지 검증한다.

 @Test
    void handleExceptionUsingExceptionHandler() {
        var response = RestAssured
                .given().log().all()
                .when()
                .get("/products?keyword=apple")
                .then().log().all()
                .extract();

        assertThat(response.statusCode())
        	.isEqualTo(HttpStatus.BAD_REQUEST.value());
    }

RestAssured로 검증한 결과, 정상적으로 HTTP 상태 코드 400 (Bad Request)과 함께 "IllegalArgumentException occurred" 메시지를 반환한 것을 알 수 있다.

@ControllerAdvice

@ControllerAdvice는 개발자가 컨트롤러에 대한 전역적인 작업을 처리할 수 있도록 해주는 어노테이션이다. 여러 컨트롤러에 걸쳐 공통적으로 발생할 수 있는 예외를 한 곳에서 처리할 수 있다. 예외 처리 외에도 데이터 바인딩 또는 모델 객체 조작 같은 작업도 중앙에서 관리할 수 있게 해준다.

@ControllerAdvice는 주로 @ExceptionHandler와 결합하여 사용되며, 특정 패키지 내의 컨트롤러나 특정 타입의 컨트롤러에 대해서만 적용할 수 있다. 이를 통해 애플리케이션의 유지보수성과 확장성을 높일 수 있다.

@ControllerAdvice가 없을 경우

@ControllerAdvice를 사용하지 않을 경우, 컨트롤러 클래스 내에서 예외 처리 로직을 함께 작성해야 한다. 이러한 방식은 관심사의 분리가 이루어지지 않아 코드의 가독성과 유지보수성이 떨어지게 된다.

@RestController
public class ProductController {

    @GetMapping("/products")
    public ResponseEntity<Void> searchProduct(@RequestParam String keyword) {
    	//...
    }

    @ExceptionHandler(value = IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
    	//...
    }

    @ExceptionHandler(value = IllegalStateException.class)
    public ResponseEntity<String> handleIllegalStateException(IllegalStateException ex) {
    	//...
    }
    
    //...
}

위의 코드는 예외 처리 로직이 컨트롤러 클래스 내에 함께 작성되어 있어, API 로직과 예외 처리 로직이 혼재하게 된다. 이는 컨트롤러를 수정할 때마다 예외 처리 로직도 함께 수정해야 하는 번거로움이 생기고, 동일한 예외 처리가 다른 컨트롤러에도 필요할 경우 중복된 코드가 발생할 수 있다.

@ControllerAdvice를 사용한 경우

이러한 문제를 해결하기 위해 @ControllerAdvice를 사용할 수 있다. 예외 처리 로직을 별도의 클래스에 분리함으로써 컨트롤러의 코드가 단순해지고, 예외 처리를 일관되게 관리할 수 있다.

먼저, 기존의 컨트롤러 클래스에서 예외 처리 관련 코드를 제거할 수 있다.

@RestController
public class ProductController {

    @GetMapping("/products")
    public ResponseEntity<Void> searchProduct(@RequestParam String keyword) {
    	//...
    }

    // 더 이상 @ExceptionHandler 메소드가 필요하지 않습니다.
}

그리고, 별도의 클래스에 예외 처리 로직을 작성한다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ProblemDetail> handleBadRequestException(BadRequestException e) {
		//...
    }

    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<ProblemDetail> handleUnauthorizedException(UnauthorizedException e) {
		//...
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ProblemDetail> handleGlobalException(Exception e) {
        // 일반적인 예외 처리 로직
    }
}

위 코드에서 GlobalExceptionHandler 클래스는 모든 컨트롤러에서 발생할 수 있는 예외를 처리한다. 이로 인해 컨트롤러 코드가 간결해지고, 공통적인 예외 처리 로직을 중앙에서 관리할 수 있게 된다. 또한, 예외 처리 로직의 중복을 방지하고, 예외 처리 전략을 쉽게 변경할 수 있는 장점이 있다.

이렇게 @ControllerAdvice@ExceptionHandler를 활용하면, 애플리케이션의 유지보수성과 확장성을 크게 향상시킬 수 있다. 예외 처리뿐만 아니라, 데이터 바인딩이나 모델 조작 로직을 전역적으로 처리하고자 할 때도 @ControllerAdvice를 활용할 수 있다.

profile
초코칩처럼 달콤한 코드를 짜자

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN