스프링/4. 스프링 MVC-2

74. ControllerAdviece

sdafdq 2023. 9. 17. 19:32

전에 @ExceptionHandler(IllegalArgumentException.class) 해서 컨트롤러 안에 에러를 처리 할 로직을 넣는 것은 나름 좋은 방법이었지만,

에러와 컨트롤러가 분리가 안됀다. 완벽히는 힘들더라도 잘 분리가 되어야만 유지보수 하기가 편하다.

 

 

@ControllerAdvice 또는 @RestControllerAdvice를 쓰면 컨트롤러와 예외처리 로직을 분리할 수 있다.

 

@Slf4j
@RestController
public class ApiExceptionV2Controller {
    @GetMapping("/api2/members/{id}")
    public ApiExceptionController.MemberDto getMember(@PathVariable("id") String id){
        if(id.equals("ex")){
            throw new RuntimeException("잘못된 사용자");
        }

        if(id.equals("bad")){
            throw new IllegalArgumentException("잘못된 입력 값");
        }
        if(id.equals("user-ex")){
            throw new UserException("사용자 오류");
        }

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

    @GetMapping("/api2/response-status-ex1")
    public String responseStatusEx1(){
        throw new BadRequestException();
    }

    @GetMapping("/api2/response-status-ex2")
    public String responseStatusEx2(){
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
    }


    @GetMapping("/api2/default-handler-ex")
    public String defaultException(@RequestParam Integer data){
        return "ok";
    }


    @Data
    @AllArgsConstructor
    static class MemberDto{
        private String memberId;
        private String name;
    }
}

기존에 있던 예외처리에 대한 @ExceptionHandler 하고 붙어있던 코드들을 분리했다.

 

 

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandler(IllegalArgumentException e){
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException e){
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX",e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e){
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }

}

@ControllerAdvice 를 붙여 따로 분리해 뒀다.

이렇게 하면 예외가 발생하면 저 ControllerAdvice를 뒤져서 저기 중 받을 수 있는 예외가 있는 지 조회해 보고, 받을 수 있는 예외가 있다면 그 로직을 수행하는 듯 하다.

 

결과도 똑같이 나온다.

 

 

@ControllerAdvice는 대상으로 지정한 컨트롤러 들에게 @ExceptionHandler, @InitBinder 기능을 부여해 주는 역할을 한다.

말 그대로 컨트롤러 어드바이스이다.

 

참고로 위는 대상을 따로 지정해두진 않았는데, 저러면 글로벌 적용이 되는거다.

 

 

대상을 지정하는 방법은 총 3가지 방법이 있다.

애노테이션마다, 아예 패키지경로를, 아예 컨트롤러 클래스를.(아예 클래스를)

 

@ControllerAdvice(annotations = RestController.class)      RestController 애노테이션을 가진 컨트롤러를 지정

@ControllerAdvice(basePackages = "org.example.controllers")       패키지 자체를 지정. 상품관리 패키지나 유저관리 패키지 따로 지정하면 유용할 듯

@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) 아예 컨트롤러 클래스 자체를 지정. 컨트롤러 뿐 아니라 아예 클래스 자체도 될거임.  인터페이스나 컨트롤러.

 

 

 

@Slf4j
@RestControllerAdvice(basePackages = "hello.exception.api")
public class ExControllerAdvice {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandler(IllegalArgumentException e){
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException e){
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX",e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e){
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }

}

이런 식으로.

 

클래스 자체를 ControllerAdvice로 등록하고, 대상 컨트롤러 지정해주고(혹은 글로벌)

거기에 @ExceptionHandler나 @InitBinder 를 정의해준다.