시작부터 예외에 대한 글

 

Exception 발생이란

Exception은 Error와 달리, 구현 로직에서 발생하는 프로그램이 모종의 이유에서 처리할 수 없는 현상을 말합니다. 구현 로직에서 발생하는 경우가 많기 때문에 개발자가 어느 정도 예상이 가능한데요. 하지만 Error와 마찬가지로 프로그램이 돌아가지 않는 것은 같습니다. 때문에 우리는 예외 처리를 통해서 예외의 종류별로 어떻게 처리할 것인지에 대해서 정할 수가 있습니다. 가장 유명한 것은 try - catch 블록이 있습니다.

Spring에서는 조금 더 나아가서 일괄적으로 예외를 처리할 수 기능이 있습니다.

 

Try - catch를 쓰는 것도 좋지만

먼저, Controller Advice를 사용하지 않았을때의 문제를 살펴 보도록 하겠습니다.

@Controller
public class Controller{
    @GetMapping("/temporary")
    public String temporary(Model model) {
        model.addAllAttributes("sample", "sample");
        try {
        	//로직
        } catch (SampleException e) {
        	//예외 처리
        }
        return "temporary";
    }
}

 

위의 코드는 제가 임의로 짠 코드입니다.

별 문제가 없으면 "sample"이라는 내용을 model에 넣어줘서 랜더링하게 되고 temporary라는 html template 형태를 반환할 것이라고 예상이 됩니다.

하지만 코드를 짜면서 저는 여러 가지 불만을 느끼게 되었는데요.

 

1. 코드의 중복

특정 Exception 특히나, 제가 커스터마이징한 Exception에 대한 처리를 일괄적으로 하고 싶습니다. 하지만, 지금의 로직이라면 다음과 같이 각 url을 처리하는 메소드마다 같은 코드를 계속해서 중복해서 넣어줘야하는 문제가 생깁니다.

 

@Controller
public class Controller{
    @GetMapping("/temporary1")
    public String temporaryOne(Model model) {
        model.addAllAttributes("sample", "sample");
        try {
        	//로직
        } catch (CustomizedException e) {
        	//커스터마이징된 예외 처리						//중복
        } catch (SampleException e) {
        	//예외 처리
        }
        return "temporaryOne";
    }
    
    @GetMapping("/temporary2")
    public String temporaryTwo(Model model) {
        model.addAllAttributes("sample", "sample2");
        try {
        	//로직
        } catch (CustomizedException e) {
        	//커스터마이징된 예외 처리						//중복
        } 
        return "temporaryTwo";
    }
}

코드의 중복 없이 특정 예외에 대해서 일괄적으로 처리를 할 수 있는 로직이나 객체가 필요합니다.

 

2. 예외 발생 시에 반환 타입 변경

현재 로직에서는 String을 반환하도록 되어 있습니다. 하지만, Exception 발생 시에 Exception에 관련된 메세지를 Exception Dto에 담아서 전달을 하고 싶습니다.

 @Controller
 public class Controller{
    @GetMapping("/temporary")
    public String temporary(Model model) {
        model.addAllAttributes("sample", "sample");
        try {
        	//로직
        } catch (SampleException e) {
        	return new ExceptionDto(e.getMessage());	// 예외 발생시에 Dto를 전달하고 싶지만..
        }
        return "temporary";
    }
}

하지만 여기서 반환 Type은 String으로 지정을 해놓았기 때문에 반환하지 않을 가능성이 매우 높습니다.

 

이런 두 가지 문제가 저한테 다가왔었고, 저는 저의 코드 리뷰에서 ControllerAdvice, RestControllerAdvice를 사용한 예외처리를 해보라는 조언을 받게 되었습니다.

@ControllerAdvice, @RestControllerAdvice with @ExceptionHandler

@Controller, @RestController와 마찬가지로, ControllerAdvice는 페이지 렌더링만, @RestControllerAdvice는 json에 여러 정보들을 담아서 반환을 할 수가 있습니다.

 

@RestControllerAdvice
public class RestControllerAdvice {
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ExceptionResponseDto> illegalArgumentExceptionResponse(
    IllegalArgumentException illegalArugmentException) {
        String message = illegalArgumentException.getMessage();
        ExceptionResponseDto exceptionResponseDto = new ExceptionResponseDto(message);
        return ResponseEntity.badRequest()
            .body(exceptionResponseDto);
    }
    
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<CustomResponseDto> customExceptionResponse(
    CustomException customException) {
        String message = customException.getMessage();
        CustomResponseDto exceptionResponseDto = new CustomResponseDto(message);
        return ResponseEntity.badRequest()
            .body(exceptionResponseDto);
    }
}

 

먼저, 원래의 Controller에서 예외가 발생하게 됩니다. 그러면 ExceptionHandler에서 해당하는 예외를 감지하게 됩니다. 특정 예외들에 대해서 일괄적으로 똑같이 처리를 할 수 있다는 점에서 앞서 언급한 문제 1번은 해결이 됩니다.

그렇게 예외를 잡게 되면, @RestControllerAdvice에서 처리를 할 수가 있는데요.

여기서도 똑같이 반환형 타입을 바꿀 수가 있습니다. 원래 String만 반환을 할 수가 있었는데, Dto를 활용하여 예외에 대한 내용이나 추가 자료를 같이 섞어서 보낼 수가 있습니다.

이렇게 @ExceptionHandler와 함께 @ControllerAdvice, @RestControllerAdvice를 활용하여, 일괄적으로 예외를 처리할 수 있게 되었습니다.

 

+ Recent posts