kimyu0218
  • [spring] REST 구현 (@RestController를 사용하는 이유/ResponseEntity를 사용하는 이유/글로벌 예외 핸들러 정의하기)
    2024년 11월 12일 09시 50분 01초에 업로드 된 글입니다.
    작성자: @kimyu0218

    @Controller 대신 @RestController를 사용하는 이유

    웹 앱을 만드는 방식에는 크게 두 가지가 있다.

    1. 백엔드가 완성된 뷰를 제공하는 앱
    2. 프론트엔드-백엔드 분리 방식의 앱

    프론트엔드-백엔드 분리 방식의 경우, 백엔드는 원시 데이터만 제공한다. 따라서 스프링 MVC 다이어그램에서 뷰 리졸버가 더 이상 필요하지 않다.

     

    스프링은 컨트롤러 클래스를 컨텍스트에 빈으로 등록하기 위해 `@Controller` 어노테이션을 사용한다. 하지만 HTTP 응답을 전달하기 위해서는 `@ResponseBody` 어노테이션이 필요하다. 이를 통해 컨트롤러가 뷰 이름이 아닌 HTTP 응답 데이터를 반환한다는 것을 디스패처 서블릿에게 알릴 수 있다.

    @RequestMapping("/member")
    @Controller	// 컨트롤러를 의미하는 스테레오 타입 어노테이션
    public class MemberController {
    	
        @GetMapping
        @ResponseBody // 뷰 이름이 아닌 HTTP 응답을 바로 반환한다
        public List<Member> findMembers() {
        	...
        }
    }

    하지만 메서드마다 `@ResponseBody` 어노테이션을 붙이는 것은 번거로운 일이다. 이를 해결하기 위해 스프링은 `@Controller`와 `@ResponseBody`를 결합한 `@RestController`를 제공한다.

    @RequestMapping("/member")
    @RestController	// HTTP 응답을 반환하는 컨트롤러
    public class MemberController {
    	
        @GetMapping
        public List<Member> findMembers() {
        	...
        }
    }

    위 코드와 같이 더 이상 `@ResponseBody`를 반복하지 않아도 된다.

     

    DTO 대신 ResponseEntity를 반환하는 이유

    위의 코드처럼 컨트롤러는 DTO 객체를 반환할 수 있다. 스프링은 기본적으로 `200 OK` 응답을 반환한다.

     

    하지만 DTO 대신 `ResponseEntity`를 반환하면 세밀한 제어가 가능하다. `ResponseEntity`는 HTTP 응답의 상태 코드, 헤더, 본문을 제어할 수 있는 기능을 제공한다.

    @RequestMapping("/member")
    @RestController
    public class MemberController {
    	
        @GetMapping
        public ResponseEntity<List<Member>> findMembers() {
        	...
            return ResponseEntity
            	.status(HttpStatus.OK)
            	.header("[NAME]", "[VALUE]")
            	.body(result);
        }
    }

     

    @RestControllerAdvice로 글로벌 예외 핸들러 정의하기

    `MemberController`의 `findMembers` 메서드에서 403이나 500 같은 예외가 발생할 수 있다면 `try ~ catch`문으로 HTTP 상태별로 다른 HTTP 응답을 보내야 한다. 하지만 이는 컨트롤러 로직이 복잡해진다는 단점이 있다.

     

    컨트롤러 로직이 길어지는 것을 방지하기 위해 `@RestControllerAdvice`와 `@ExceptionHandler`를 활용할 수 있다.

    • `@RestControllerAdvice` : `@ControllerAdvice`와 `@ResponseEntity` 어노테이션을 결합한 것으로, 컨트롤러의 메서드를 가로채 예외 처리 메서드를 구현할 수 있다.
    • `@ExceptionHandler` : 예외를 처리하는 메서드 앞에 붙이는 어노테이션으로, `value` 속성에 해당 메서드가 처리할 수 있는 예외 클래스를 설정한다.
    @RestControllerAdvice // AOP를 통해 RestController의 메서드를 가로챈다
    public class GlobalExceptionHandler {
    	
        // BadRequestException 클래스를 예외 처리한다
        @ExceptionHandler({BadRequestException.class})
        public ResponseEntity<ErrorResponse> handleBadRequestException(final BadRequestException ex) {
        	return ResponseEntity.status(HttpStatus.BAD_REQUEST)
            	.body(new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()));
        }
        ...
    }

    이처럼 어플리케이션에서 발생할 수 있는 모든 예외를 핸들링하면 클라이언트에게 시스템 내부 정보가 노출되는 것을 사전에 예방할 수 있다.

    🔗 GlobalExceptionHandler 소스코드

    참고자료

    댓글