본문 바로가기
TIL

[Spring] Exception Handler, Controller Advice

by thegreatjy 2024. 1. 25.
728x90

exception handler

  • 컨트롤러 클래스의 메서드에서 @ExceptionHandler(value = 처리할예외.class)
  • 해당 컨트롤러 내에서 처리하고 싶은 예외처리에 대한 메서드를 생성 후, 애너테이션을 붙여준다.
  • 하나의 컨트롤러 클래스에서 발생하는 예외 처리를 한다.
  • target : 어디서 사용할 것인지 명시. 메서드에서 사용될 것이다.
  1. 컨트롤러 레이어에서 발생한 예외처리
  • 예외 발생
@GetMapping("exc_in_controller")
    public void controllerException() {
        throw new NullPointerException(); // controller에서 NullPointerException 예외발생 -> @ExceptionHandler(value = NullPointerException.class) 선언된 메서드에서 처리된다.
    }
  • 예외 처리
@GetMapping("/check/exception/null")
    @ExceptionHandler(value = NullPointerException.class)
    public String nullException() throws Exception{
        // ...
        return "error/null";
    }
  1. 서비스 레이어에서 발생한 예외처리
  • 예외 발생
// controller

@GetMapping("exc_in_service")
    public void serviceException() {
            System.out.println("스테디 ");
            statisticService.makeException(); 
                // service에서 ArrayIndexOutOfBoundsException 예외발생 -> 결국 컨트롤러에서 예외 발생된 것과 동일
    }
// service

public class StatisticService {

    public void makeException() throws ArrayIndexOutOfBoundsException{
        throw new ArrayIndexOutOfBoundsException();
    }
}
  • 예외 처리
@GetMapping("check/exception/oob")
  @ExceptionHandler(value = ArrayIndexOutOfBoundsException.class)
  public String oobException() throws Exception{
      // ...
      return "error/oob";
  }
  1. 레포지터리 레이어에서 발생한 예외처리
  • 2와 동일하게 throw하여 처리
  • 예외 발생
// controller

@GetMapping("exc_in_repository")
    public void repositoryException() {
        statisticService.makeExceptionInRepository(); 
        // service->repository에서 throws로 ArrayIndexOutOfBoundsException 예외발생  -> 결국 컨트롤러에서 예외 발생된 것과 동일
    }
public void makeExceptionInRepository() throws ArrayIndexOutOfBoundsException{
        try {
            statisticRepository.makeExceptionInRepository();
        }catch (Exception e){
            throw new ArrayIndexOutOfBoundsException();
        }finally {
            throw new ArrayIndexOutOfBoundsException();
        }
    }
// repository

@Repository
public class StatisticRepository {
    public Exception makeExceptionInRepository() throws ArrayIndexOutOfBoundsException{
        throw new ArrayIndexOutOfBoundsException();
    }
}

controller advice

  • 모든 컨트롤러에 대해서 예외처리 가능
  • @Controller인 컨트롤러 클래스애서 발생하는 예외를 처리한다.
  • 전역 예외 처리 가능
  1. 컨트롤러 클래스에서 예외 발생
@Controller
public class IOExceptionTestController {
    @GetMapping("/make_io_exception")
    public void createIOException() throws IOException{
        throw new IOException();    // @ControllerAdvice 호출됨
    }
}
  1. @ControllerAdvice 선언된 컨트롤러 클래스에서 예외 처리하도록 만듦
@Slf4j
@ControllerAdvice
public class ErrorControllerAdvice {
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(IOException.class)   // 처리하고 싶은 exception
    public String handle(IOException exception, HttpServletRequest request){
        System.out.println("@ControllerAdvice : IOException");
        return "error/error";
    }
}
  1. 1에서 /make_io_exception를 요청하면 2 클래스에서 예외처리를 해줌

자주 사용되는 방법

  • 제일 부모 예외 클래스를 상속받은 커스텀 예외 클래스를 만듦.

  • 이 예외에 대한 처리를 해서 모든 예외에 처리.

  • ResponseStatus 으로 에러의 이유를 숨김

  • controller advice, error controller 순서

에러와 예외의 차이

  • 에러 : 컴퓨터의 컴파일링 과정에서 코드 상의 잘못된 점. 코드가 잘못되어 실행되지 않음.
  • 예외 : 개발자가 예외 상황에서 처리해주는 것. 실행 도중 문제(예외)가 발생하더라도 대응 코드를 작성(예외 처리)하여 예방 가능하다.

@ResponseStatus

커스텀 예외 생성 및 예외 처리

  1. 커스텀 예외 클래스 생성
  • 어떤 부모 클래스를 상속받게 만듦.
  • 이 예제에서는 RuntimeException을 상속받음
@Getter
@Slf4j
public class CustomExceptionTemp extends RuntimeException{
    private HttpStatus httpStatus;
    private String message;

    public CustomExceptionTemp (HttpStatus httpStatus, String message){
        this.httpStatus = httpStatus;
        this.message = message;
    }
}
  1. 컨트롤러 메서드에서 예외 발생 시, 커스텀 예외를 발생시키도록 만듦.
@Controller
@Log4j2
public class CustomExceptionController {
    // custom 예외 발생
    @GetMapping("find-member-info")
    public ResponseEntity<?> findMemberInfo(){
        throw new CustomExceptionTemp(HttpStatus.NOT_FOUND, "회원이 존재하지 않습니다.");
    }

    // RuntimeException 예외 발생 -> CustomExceptionTemp 발생
    @GetMapping("find-member-info-2")
    public ResponseEntity<?> findMemberInfo_2(){
        try{
            log.info("강제 RuntimeException invoke > catch > 생성한 CustomException throw > advice가 처리");
            throw new RuntimeException();
        }catch(Exception e){
            throw new CustomExceptionTemp(HttpStatus.NOT_FOUND, "회원이 존재하지 않습니다.");
            //
        }
    }
}
  1. @ControllerAdvice 클래스의 @ExceptionHandler 선언한 메서드를 생성
@Slf4j
@Log4j2
@ControllerAdvice
public class ErrorControllerAdvice {
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(CustomExceptionTemp.class)
    public String handle(CustomExceptionTemp exception, HttpServletRequest request){
        log.info("@ControllerAdvice : CustomExceptionTemp");
        return "error/error";
    }
}
  1. localhost:포트/find-member-info-2 요청 시, 로그에 다음과 같이 출력됨

  1. 참고로 CustomException 예외가 발생한 컨트롤러 클래스 내부에 해당 예외에 대한 @ExceptionHandler가 존재한다면 그 메서드가 @ControllerAdvice보다 먼저 실행된다.
728x90