SPRING/Spring MVC

[SpringMVC] API 예외처리하기(3) - @ExceptionHandler, @ControllerAdvice

IT록흐 2023. 9. 13. 00:33
반응형

 

 

[SpringMVC] API 예외처리하기(2) - HandlerExceptionResolver

컨트롤러에서 에러가 발생하면 복잡해진다. 클라이언트 요청 -> WAS -> 컨트롤러 (에러발생) -> WAS -> 컨트롤러 -> 클라이언트 응답 [SpringMVC] API 예외처리하기(1) - BasicErrorController 위 그림은 Spring에서

lordofkangs.tistory.com

 

지난 포스팅에서 API 호출에서 발생하는 에러를 처리하기 위해, ExceptionResolver를 직접 만들어 구현해보았다. 

 

 

 

Controller에서 에러가 발생하면 디스패처 서블릿으로 전파된다. 디스패처 서블릿은 에러가 WAS로 전파되지 않도록 에러를 핸들링 할 수 있는 ExceptionResolver를 탐색한다. 오류 종류에 따라 ExceptionResolver 구현체를 다양하게 생성하면 다양한 오류에 대응할 수 있다. 

 

그러나 한 가지 문제가 있다. 

 

ExceptionResolver가 너무 비대해진다. 

 

 

 

ExceptionResolver는 에러상태코드를 바꾸고 클라이언트로 응답할 에러데이터를 생성하고 HTTP Body 영역에 에러데이터를 write 할 수 있도록 Response 객체를 설정하고 WAS에 에러가 전파되지 않도록 ModelAndView를 정상 반환한다. 모든 에러마다 이 과정을 반복하여 작성하면 비효율적이다. 

 

그러므로 가변적인 부분을 따로 분리하는 것이 좋다. 

 

SpringBoot는 ExceptionResolver에서 가변적인 부분은 개발자가 개발하도록 넘기고 ExceptionResolver가 참조할 수 있도록 어노테이션으로 표시하는 방향으로 기능을 제공한다. 

 

 

 

 

에러상태코드 변환이나 에러데이터 생성은 API 호출에 따라 언제든 변하는 코드이다. 그러므로 이는 개발자가 필요에 따라 개발하고 @ResponseStatus와 @ExceptionHandler로 표시를 남기면, ExceptionResolver가 알아서 해당 코드를 참조하는 방식이다. 이로써 개발자는 더이상 반복적으로 ExceptionResolver를 개발할 필요가 사라진다. 

 

@ResponseStatus는 ResponseStatusExceptionResolver가 처리하고

@ExceptionHandler ExceptionHandlerExceptionResolver가 처리한다. 

 

두 리졸버는 SpringBoot가 ExceptionResolver 구현체로 등록해놓았다. 그러므로 우리는 오류에 따라 어떻게 에러상태코드를 바꿀지 어떤 에러데이터를 생성할지만 코드로 구현하면 된다. 

 

그럼 Controller를 보자. 

 

@Slf4j
@RestController
public class ExceptionController {

    // == 요청 받는 Controller 메소드 ==
    
    @GetMapping("/api/members/{id}")    
    public MemberDto getMember(@PathVariable("id") String id){
    
        // RuntimeException 발생
        if(id.equals("ex")){
            throw new RuntimeException("잘못된 사용자");
        }
        
        // IlleagalArgumentException 발생
        if(id.equals("bad")){
            throw new IllegalArgumentException("잘못된 입력값");
        }

        return new MemberDto(id,"hello " + id);       
    }
    
    // == ExceptionResolver의 ExceptionHandler ==
    
    // IllegealArgumentException이 발생한 경우
    @ResponseStatus(HttpStatus.BAD_REQUEST) // 에러상태코드 변환 : 500 -> 400
    @ExceptionHandler(IllegalArgumentException.class) 
    public ErrorResult illegalExHandler(IllegalArgumentException e) {
    
        // 객체 반환하기 ( JSON으로 변환 )
        return new ErrorResult("BAD",e.getMessage()); // 에러데이터 생성
        
    }
    
    // RuntimeException이 발생한 경우
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<ErrorResult> userExHandler(RuntimeException e){
    
        // 에러데이터 생성
        ErrorResult errorResult = new ErrorResult("USER-EX",e.getMessage());
        
        // ResponseEntity로 에러상태코드 변환하기
        return new ResponseEntity<>(errorResult,HttpStatus.BAD_REQUEST); 
        
    }



}

 

 

Controller는 두 구간으로 나뉜다.

 

1)  요청과 매핑되는 Controller 메소드

2) ExceptionResolver가 참조하는 ExceptionHandler 코드 

 

클라이언트에서 요청이 들어오면 1) 구간에서 처리한다. 그러다가 에러가 발생하면 ExceptionHandlerExceptionResolver가 동작하는데, 이때 에러가 난 컨트롤러의 2) 구간에 선언된 @ExceptionHandler를 탐색한다. 그러므로 특정 Controller에서 발생한 에러를 특정한 로직으로 유연하게 처리할 수 있게 된 것이다. 

 

위 코드를 보면 id로 ex가 들어오면 RuntimeException이 발생한다. 같은 컨트롤러 안에서 @ExceptionHandler로 RuntimeException이 발생한 경우 처리되는 로직이 정리되어 있다. 에러상태코드도 ResponseEntity를 반환하여 변경할 수도 있다. 아니면 IllegalArgumentException이 발생한 경우처럼 @ResponseStatus 어노테이션으로 처리 할 수도 있다. 

 

이처럼 ExceptionResolver를 구현할 필요없이 가변적으로 에러데이터 생성 및 에러상태코드 변경만 어노테이션으로 구현해주면 된다. 이는 특정 컨트롤러 전용으로 동작하므로, 컨트롤러마다 다양한 오류처리가 가능해진다. 

 

 

@ControllerAdvice

 

컨트롤러는 비즈니스 로직을 처리하는 책임이 있는 클래스이다.

 

1)  요청과 매핑되는 Controller 메소드

2) ExceptionResolver가 참조하는 ExceptionHandler 코드 

 

그런데 두 가지 책임을 갖게 되었다. 하나의 클래스는 하나의 책임만 가지도록 해야 객체지향적 설계가 가능하다. 그러므로 2) 영역을 분리해보자. SpringBoot는 @ControllerAdvice를 제공하여 ExceptionHandler 코드가 분리될 수 있도록 지원한다. 

 

 

Controller ( 요청을 처리하는 컨트롤러 )

@Slf4j
@RestController
public class ExceptionController {
    
    @GetMapping("/api/members/{id}")    
    public MemberDto getMember(@PathVariable("id") String id){
    
        // RuntimeException 발생
        if(id.equals("ex")){
            throw new RuntimeException("잘못된 사용자");
        }
        
        // IlleagalArgumentException 발생
        if(id.equals("bad")){
            throw new IllegalArgumentException("잘못된 입력값");
        }

        return new MemberDto(id,"hello " + id);       
    }
    
}

 

@ControllerAdvice ( Exception을 처리하는 컨트롤러 ) 

@Slf4j
@RestControllerAdvice(basePackages = "hello.exception.api")
public class ExControllerAdvice {    
        
    // IllegealArgumentException이 발생한 경우
    @ResponseStatus(HttpStatus.BAD_REQUEST) // 에러상태코드 변환 : 500 -> 400
    @ExceptionHandler(IllegalArgumentException.class) 
    public ErrorResult illegalExHandler(IllegalArgumentException e) {
    
        // 객체 반환하기 ( JSON으로 변환 )
        return new ErrorResult("BAD",e.getMessage()); // 에러데이터 생성
        
    }
    
    // RuntimeException이 발생한 경우
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<ErrorResult> userExHandler(RuntimeException e){
    
        // 에러데이터 생성
        ErrorResult errorResult = new ErrorResult("USER-EX",e.getMessage());
        
        // ResponseEntity로 에러상태코드 변환하기
        return new ResponseEntity<>(errorResult,HttpStatus.BAD_REQUEST); 
        
    }
    
}

 

두 개의 책임이 각각의 클래스로 분리되었다. 이로써 Controller는 깔끔해졌다. 

 

에러가 발생하면 @ControllerAdvice로 선언된 클래스의 메소드가 호출된다. @ControllerAdvice는 경로를 설정할 수 있는데, 이는 ExceptionHandler를 적용 가능한 범위를 가리킨다. 이로써 컨트롤러에서 오류가 발생해도 그에 맞는 적절한 오류를 처리가 가능해진다. 

 

@RestControllerAdvice

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
		// 중략...
}

 

@RestControllerAdvice는 오류데이터를 HTTP Body 영역의 Message로 전달하는 어노테이션이다. 어노테이션을 열어보면 위 코드와 같다. @ControllerAdvice와 @ResponseBody 기능을 모두 가진 어노테이션이다. 

 

정리하면,

 

@ExceptionHandler는 현재 실무에서 가장 많이 사용되고 있는 API 에러처리 방식으로 효율적이고 유연한 에러처리가 가능하다.  또한 @ControllerAdvice 어노테이션을 사용하면 책임을 분리하여 객체지향적으로 에러처리가 가능해진다. 

 

 


 

 

참고자료

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

반응형