본문 바로가기
SPRING

[Spring] Exception Custom

by 킹명주 2023. 2. 13.

최근에 개발을 많이 놓아서 블로그에 Spring 관련 지식을 정리하지 못했다. 이제부터 다시 개발을 진행해보고자 한다.

이번에는 개발을 진행하면서 가장 중요한 부분인 Spring Exception 처리에 대해 소개하고자 한다.

 

Exception 소개 뿐만 아니라 RuntimeException을 상속받아 customizing 하는 과정을 상세하게 소개하겠다.


Exception 왜 필요한가?

 

API를 개발하면서 Exception 처리가 왜 필요할까?

정답은 없지만 필자가 생각하는 것은 크게 두 가지이다.

1. 클라이언트와 서버에서 어떤 오류인지 파악하여 바로 트러블 슈팅이 가능하다.

2. 잠재적 위험을 Exception 처리를 통해 방지한다. (ex. return null, parameter issue ... )

 

그렇다면 Exception에 대한 Custom은 왜 필요할까? 

예를 들어 Http Status Code 404는 Not Found이다. 만약, 클라이언트가 URI를 잘못 입력한 경우 404 status code가 출력될 것이다.

그런데 서버에서 resource를 찾지 못했을 때도 404를 준다고 가정해보자. 그렇다면 클라이언트는 404 status code를 보고 URI가 잘못된 것인지 서버의 resource를 찾지 못한 것인지 구별할 수 없다. 이럴 경우 exception을 custom하여 사용하는 것이다.

 

필자는 다음과 같은 response 형식을 즐겨 사용한다.

status code: 404

  code: 'F001',

  message: 'Resource not found'

}

 

status code: 404

  code: 'F002',

  message: 'Url not found'

}

이것과 Not found error의 일반적 형태인 아래의 그림과 비교해보면 어떤가?

확실하게 어떤 에러인지 구별할 수 있고 협업을 진행하면서 클라이언트에게 이쁨 받을 수 있을 것이다.

 


HTTP Status code

 

본격적으로 구현에 들어가기 앞서 status code를 적절하게 사용하기 위해 의미를 알아보자. 필자가 주로 많이 사용하는 Status code만 정리했다. 모든 코드를 알고싶다면 위키백과를 참고하면 된다.

https://ko.wikipedia.org/wiki/HTTP_%EC%83%81%ED%83%9C_%EC%BD%94%EB%93%9C

 

HTTP 상태 코드 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 아래는 HTTP(하이퍼텍스트 전송 프로토콜) 응답 상태 코드의 목록이다. IANA가 현재 공식 HTTP 상태 코드 레지스트리를 관리하고 있다. 모든 HTTP 응답 코드는 5개의

ko.wikipedia.org

400 Bad Request

클라이언트의 잘못된 문법으로 인하여(parameter 문제 등) 서버가 이해할 수 없음을 의미한다.

 

401 Unauthorized

클라이언트가 인증되지 않았다는 것을 의미한다.

 

403 Forbidden

클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않다는 것을 의미한다.

(401과 다른 점은 서버가 클라이언트가 누구인지 알고 있다.)

 

404 Not Found

요청받은 리소스를 찾을 수 없을 때 사용한다.

 

405 Method Not Allowed

URI는 맞지만 Method가 잘못된 경우에 사용한다.

 

409 Conflict

이 요청이 현재 서버의 상태와 충돌될 때 사용한다.

 

500 Internal Server Error

서버 문제일 때 사용한다.

 


Exception Custom

 

본격적으로 코딩을 진행해보자. 우선 프로젝트를 생성하자. 

https://start.spring.io/

참고로 자바 11의 경우 spring boot 3버전을 사용할 수 없다. 그러므로 2.7.9버전으로 진행해주면 된다.

 

간단하게 controller, exception, service, dto package 그리고 각각 class를 만들어 다음과 같은 구조로 만들어 준다.

예외는 Bad Request 400번만 구현할 예정이다. 프로젝트를 진행하면서 필요한 status code를 추가하여 사용하면 된다.

ErrorHandler는 말그대로 각각 구현한 exception을 handling해주는 class라고 생각하면 된다.

가장 먼저 dto와 controller를 구현해보자.

ApiResponse.java

package com.example.demo.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ApiResponse {
    private String message;
}

TestController.java

package com.example.demo.controller;

import com.example.demo.dto.ApiResponse;
import com.example.demo.service.TestService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    private final TestService testService;
    public TestController(TestService testService){
        this.testService=testService;
    }
    @GetMapping("/test/{id}")
    public ResponseEntity<ApiResponse> getTest(@PathVariable Integer id){
        return ResponseEntity.status(HttpStatus.OK)
                .body(testService.getTest(id));
    }
}

 

4를 싫어하기 때문에 /test/4 형태로 get 요청이 온다면 400 bad request를 내보낼 생각이다. 또한, request payload에는 message를 전달하여 클라이언트가 어떤 이슈인지 명확하게 알 수 있도록 한다.

 

중요한 exception 구현에 대해 살펴보자

BadRequestException.java

package com.example.demo.exception;

public class BadRequestException extends RuntimeException{
    public BadRequestException(String message){
        super(message);
    }
}

RuntimeException으로 부터 상속받아 입력받은 message만 부모에게 전달해주는 코드이다. 

 

ErrorHandler.java

package com.example.demo.exception;

import com.example.demo.dto.ApiResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice(annotations = RestController.class)
public class ErrorHandler {
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ApiResponse> badRequestError(BadRequestException e){
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ApiResponse.builder()
                        .message(e.getMessage())
                        .build());
    }
}

만약, 400 error 말고 다른 코드를 사용하려면 exception 파일을 하나 만들어주고 해당 handler에 bad request 코드와 똑같이 구현하면 된다.

 

TestService.java

package com.example.demo.service;

import com.example.demo.dto.ApiResponse;
import com.example.demo.exception.BadRequestException;
import org.springframework.stereotype.Service;

@Service
public class TestService {
    public ApiResponse getTest(Integer id){
        if(id==4){
            throw new BadRequestException("킹명주는 숫자 4를 싫어해");
        }
        return ApiResponse.builder()
                .message("요청을 성공적으로 수행했습니다.")
                .build();
    }
}

만약 path parameter가 4인 경우 400 status code와 "킹명주는 숫자 4를 싫어해"라는 메시지가 출력될 것이다. 그 외에는 요청을 성공했다는 메시지가 출력된다.

 

요청에 성공한 경우

 

요청에 실패한 경우


혼자 개발을 할 때에는 예외처리에 많은 신경을 쓰지 못했다. 그러나 협업 과정을 통해 exception의 중요성에 대해 알게 되었고 custom 하는 방법에 대해서도 숙지하게 되었다. 나름 exception custom하는 것에는 노하우가 생겼다.

 

크게 어려운 작업은 아니니 필요하다면 따라해보면 금방 구현할 것이다.

'SPRING' 카테고리의 다른 글

[Spring] AOP 맛보기  (7) 2023.02.21
[Spring] @Setter vs @Builder  (2) 2023.02.20
[Spring] Google Login API 사용하기  (12) 2022.10.10
[Spring] JPA CASCADE  (2) 2022.08.31
[Spring] JWT 실습 - 1  (2) 2022.07.27