[Spring] 커스텀 예외

WOOK JONG KIM·2022년 11월 7일
0
post-thumbnail

대부분의 상황에서는 자바에서 이미 적절한 상황에 사용할 수 있도록 제공하는 표준 예외 사용시 해결 가능

커스텀 에외를 만들어서 사용하면 네이밍에 개발자의 의도를 담을 수 있기에 이름만으로도 어느 정도 예외 상황을 짐작할 수 있다
-> 표준 예외 사용 시 보통 에외 타입의 이름만으로 이해하기 어려운 경우가 있어 예외 메세지를 상세히 작성해야 하는 번거로움 있음

커스텀예외 사용 시 애플리케이션에서 발생하는 예외를 개발자가 직접 관리하기 수월
-> 책임 소재를 애플리케이션 내부로 가져올 수 있게 됨

또한 표준 예외는 의도치 않은 예외 상황도 정해진 예외 처리 코드에서 처리하기 때문에 어디서 문제가 발생했는지 확인하기 어려운 반면 커스텀 예외로 관리 시 의도하지 않았던 부분에서 발생한 에외는 개발자가 관리하는 예외 처리 코드가 처리하지 않아 개발과정에서 혼동 여지가 줄어듬


커스텀 예외 클래스 생성하기

커스텀 예외는 에외가 발생하는 상황에 해당하는 상위 예외 클래스 상속받음

Exception 클래스 커스텀 예외 만들기

Exception 클래스

public class Exception extends Throwable {
    static final long serialVersionUID = -3387516993124229948L;

 
    public Exception() {
        super();
    }

  
    public Exception(String message) {
        super(message);
    }

   

    public Exception(String message, Throwable cause) {
        super(message, cause);
    }

   
    public Exception(Throwable cause) {
        super(cause);
    }

    protected Exception(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

코드를 보면 Throwable 클래스의 생성자를 호출하는 것을 볼 수 있음

Throwable 클래스

 public Throwable() {
        fillInStackTrace();
    }

    public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }

    
    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }

   
    public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }

Exception 클래스는 Throwable 클래스의 생성자를 호출하게 되며, message 변수의 값을 detailMessage 변수로 전달 받음
-> 커스텀 예외 생성 경우에도 이 message 변수 사용

그리고 HttpStatus(enum형)를 커스텀 예외 클래스에 포함시키면 앞선 방법처럼 핸들러 안에서 선언해서 사용하는 것이 아닌 예외 클래스만 전달받으면 그 안에 내용이 포함돼 있는 구조로 설계 가능

public enum HttpStatus {

...

 BAD_REQUEST(400, HttpStatus.Series.CLIENT_ERROR, "Bad Request"),
    UNAUTHORIZED(401, HttpStatus.Series.CLIENT_ERROR, "Unauthorized"),
    PAYMENT_REQUIRED(402, HttpStatus.Series.CLIENT_ERROR, "Payment Required"),
    FORBIDDEN(403, HttpStatus.Series.CLIENT_ERROR, "Forbidden"),
    NOT_FOUND(404, HttpStatus.Series.CLIENT_ERROR, "Not Found"),
    METHOD_NOT_ALLOWED(405, HttpStatus.Series.CLIENT_ERROR, "Method Not Allowed"),
    
...

private HttpStatus(int value, Series series, String reasonPhrase) {
        this.value = value;
        this.series = series;
        this.reasonPhrase = reasonPhrase;
    }

    public int value() {
        return this.value;
    }

    public Series series() {
        return this.series;
    }

    public String getReasonPhrase() {
        return this.reasonPhrase;
    }

Http Status는 value, series,reasonPhrase 변수로 구성된 객체 제공
-> 이는 흔히 보는 Http 응답 코드와 메세지

커스텀 에외 클래스를 생성하는데 필요한 내용

  • 에러 타입 : HttpStatus의 reasonPhrase
  • 에러 코드 : HttpStatus의 value
  • 메세지 : 상황별 상세 메세지

커스텀 예외 클래스 구조

도메인 레벨 표현을 위한 열거형 생성(ExceptionClass)

public class Constants {

    public enum ExceptionClass{

        PRODUCT("Product");

        private String exceptionClass;

        ExceptionClass(String exceptionClass){
            this.exceptionClass = exceptionClass;
        }

        public String getExceptionClass(){
            return exceptionClass;
        }

        @Override
        public String toString() {
            return getExceptionClass() + "Exception.";
        }
    }
}

Constants 클래스 생성 후 ExceptionClass 내부에 생성
-> 확장성을 위해 통합 관리하는 클래스를 생성하였음

ExceptionClass라는 열거형은 커스텀 예외 클래스에서 메세지 내부에 어떤 도메인에서 문제가 발생했는지 보여주는데 사용

지금까지는 상품이라는 도메인에서만 실습 코드를 작성하여 PRODUCT라는 상수만 선언하였음

커스텀 예외 클래스 생성(CustomException.java)

public class CustomException extends Exception{
    
    private Constants.ExceptionClass exceptionClass;
    private HttpStatus httpStatus;
    
    public CustomException(Constants.ExceptionClass exceptionClass, HttpStatus httpStatus, String message){
        super(exceptionClass.toString() + message);
        this.exceptionClass = exceptionClass;
        this.httpStatus = httpStatus;
    }
    
    public Constants.ExceptionClass getExceptionClass(){
        return exceptionClass;
    }
    
    public int getHttpStatusCode(){
        return httpStatus.value();
    }
    
    public String getHttpStatusType(){
        return httpStatus.getReasonPhrase();
    }
    
    public HttpStatus getHttpStatus(){
        return httpStatus;
    }
    
}

CustomException을 처리하는 handleException() 메서드

@ExceptionHandler(value = CustomException.class)
    public ResponseEntity<Map<String, String>> handleException(CustomException e,
                                                               HttpServletRequest request){
        HttpHeaders responseHeaders = new HttpHeaders();
        LOGGER.error("Advice 내 handleException 호출, {}, {}", request.getRequestURI(), e.getMessage());
        
        Map<String, String> map = new HashMap<>();
        map.put("error type", e.getHttpStatusType());
        map.put("code", Integer.toString(e.getHttpStatusCode()));
        map.put("message", e.getMessage());
        
        return new ResponseEntity<>(map, responseHeaders, e.getHttpStatus());
    }

기존의 핸들러 메서드와 달리 예외 발생 시점에 HttpStatus를 정의해 전달하기에 클라이언트 요청에 따라 유동적으로 응답코드 설정 가능


Swagger로 테스트 하기 위해 컨트롤러 메서드 생성

@GetMapping("/custom")
    public void getCustomException() throws CustomException{
        throw new CustomException(ExceptionClass.PRODUCT, HttpStatus.BAD_REQUEST, "getCustomException 메서드 호출");
    }

Response Body를 통해 예외 발생 지점에서 설정한 값이 정상적으로 담겨 클라이언트로 응답한 것을 볼 수 있다

profile
Journey for Backend Developer

0개의 댓글