[Java]Exception

윤재열·2022년 7월 29일
0

Java

목록 보기
56/71
post-custom-banner

1.Java 의 Exception

  • java 에는 Checked ExceptionUnchecked Exception 이 존재합니다.

  • 이둘은 헷갈리지만 사실상 큰 차이가 존재합니다.

  • RuntimeException을 상속하지 않는 클래스는 Checked Exception 로 분류할 수 있고,

RuntimeException을 상속하는 클래스는 Unchecked Exception으로 분류할 수 있습니다.

1.1 Checked Excetpion

  • 예외처리 필수
    • try catch로 잡아서 예외를 처리하거나 상위 메서드로 넘겨줘야합니다.
  • Transaction 기본 롤백 대상이 아니라서 롤백 처리하려면 추가 처리가 필요합니다.
  • Compile 단계에서 체크합니다.
  • Error 와 RuntimeException을 상속하지 않은 예외들을 모두 포함합니다.
  • 대표적인 예 ) Error , FileNotFoundException, ClassNotFoundException

1.2 Unchecked Exception(RuntimeExcetpion)

  • 예외 처리 필수 아님
    • 예측할 수 없는 예외라서 필수 처리 불가능
  • Transaction 롤백 대상
    • @Transactional rollbakFor 에 기본 옵션이 들어가있기 때문에 예외 발생 시 롤백 처리됨
  • 런타임 단계에서 체크
  • 대표적인 예) NullPointerException, ClassCastException

2. @ResponseStatus

@RestponseStatus(code= HttpStatus.NOT_FOUND, reason = "Data Not Found")
public class DataNotFoundException extends RuntimeExcetpion{}
  • spring 3 부터는 HTTP Status 와 Response를 제공하는 @ResponseStatus 어노테이션을 사용할 수 습니다.
    • 개발자가 정의한 Exception이 발생하면 해당 Status 와 Message를 전달합니다.

3.Spring 전역으로 공통 Exception 처리하기

throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No Data");
  • Spring 5 부터 제공하는 ResponseStatusException 의 일반적인 사용입니다.
  • 생성자로 HTTP Status 와 String 만을 받습니다.
  • 만약 없는 데이터의 종류가 다르다면 ?? No Member Data, No Profile Data 등등.. 이렇게 표현해야 합니다.
  • 하지만 이렇게 명확한 규격 없이 String 으로만 받으면 여러 사람들이 작업할 때 중복된 응답을 주거나 이미 있는 응답을 새로 만들거나, 오타로 인해 실수할 가능성도 있습니다.
  • 위와 같은 이유로 Spring Exception 처리는 다른 방법으로 하게 되었습니다.

    • ErrorCode : 핵심, 모든 예외 케이스를 이곳에서 관리함.
    • CustomException : 기본적으로 제공되는 Excetpion 외에 사용
    • ErrorResponse : 사용자에게 JSON 형식으로 보여주기 위해 에러 응답 형식 지정
    • GlobalExceptionHandler : Custom Exception Handler
      • @ControllAdvice : 프로젝트 전역에서 발생하는 Exception을 잡기 위한 클래스
      • @ExceptionHandler : 특정 Exception을 지정해서 별도로 처리해줌

3.1 Error 관련 properties 설정

# applicatioin.yml
server:
 error:
  include-exception: false	//response에 Exception을 표시할지
  include-message: always 	//response에 Exception Message를 표시할지(never | always " on_param)
  include- stacktrace: on_param 	//response에 Stack Trace를 표시할지(never | always " on_param)
  whitelabel.enabled: true      // 에러 발생 시 Spring 기본 에러 페이지 노출 여부

3.2 ErrorCode

@Getter
@AllArgsConstructor
public enum ErrorCode {

    /* 400 BAD_REQUEST : 잘못된 요청 */
    INVALID_REFRESH_TOKEN(BAD_REQUEST, "리프레시 토큰이 유효하지 않습니다"),
    MISMATCH_REFRESH_TOKEN(BAD_REQUEST, "리프레시 토큰의 유저 정보가 일치하지 않습니다"),
    CANNOT_FOLLOW_MYSELF(BAD_REQUEST, "자기 자신은 팔로우 할 수 없습니다"),

    /* 401 UNAUTHORIZED : 인증되지 않은 사용자 */
    INVALID_AUTH_TOKEN(UNAUTHORIZED, "권한 정보가 없는 토큰입니다"),
    UNAUTHORIZED_MEMBER(UNAUTHORIZED, "현재 내 계정 정보가 존재하지 않습니다"),

    /* 404 NOT_FOUND : Resource 를 찾을 수 없음 */
    MEMBER_NOT_FOUND(NOT_FOUND, "해당 유저 정보를 찾을 수 없습니다"),
    REFRESH_TOKEN_NOT_FOUND(NOT_FOUND, "로그아웃 된 사용자입니다"),
    NOT_FOLLOW(NOT_FOUND, "팔로우 중이지 않습니다"),

    /* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */
    DUPLICATE_RESOURCE(CONFLICT, "데이터가 이미 존재합니다"),

    ;

    private final HttpStatus httpStatus;
    private final String detail;
}
  • 에러 형식을 ENUM 클래스로 정의합니다.
  • 응답으로 내보낼 HttpStatus 와 메세지로 사용할 String을 갖고 있습니다.
  • @ResponseStatusException과 비슷해 보이지만, 차이점은 개발자가 정의한 새로운 Exception을 모두 한 곳에서 관리하고 재사용 할수 있습니다.

3.3 CustomException

@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException {
    private final ErrorCode errorCode;
}
  • 전역으로 사용할 CustomException 입니다.
    -RuntimeException을 상속받아서 Unchecked Exception 으로 활용합니다.
  • 생성자로 ErrorCode를 받습니다.

3.4 ErrorResponse

@Getter
@Builder
public class ErrorResponse {
    private final LocalDateTime timestamp = LocalDateTime.now();
    private final int status;
    private final String error;
    private final String code;
    private final String message;

    public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(ErrorResponse.builder()
                        .status(errorCode.getHttpStatus().value())
                        .error(errorCode.getHttpStatus().name())
                        .code(errorCode.name())
                        .message(errorCode.getDetail())
                        .build()
                );
    }
}
  • 실제로 유저에게 보낼 응답 Format 입니다.
  • 일부러 500에러 났을 때와 형식을 맞췃습니다.
  • ErrorCode를 받아서 ResponseEntity<ErrorResponse>로 변환해줍니다.

3.5 @ControllerAdvice and @ExceptioHandler

  • @ControllerAdvice는 프로젝트 전역에서 발생하는 모든 예외를 잡아줍니다.
  • @ExceptionHandle 는 발생한 특정 예외를 잡아서 하나의 메서드에서 공통 처리해 줄 수 있게 해줍니다.
  • 따라서 둘을 같이 사용하면 모든 예외를 잡은 후에 Exception 종류별로 메서드를 공통 처리할 수 있습니다.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = { ConstraintViolationException.class, DataIntegrityViolationException.class})
    protected ResponseEntity<ErrorResponse> handleDataException() {
        log.error("handleDataException throw Exception : {}", DUPLICATE_RESOURCE);
        return ErrorResponse.toResponseEntity(DUPLICATE_RESOURCE);
    }

    @ExceptionHandler(value = { CustomException.class })
    protected ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
        log.error("handleCustomException throw CustomException : {}", e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }
}
  • View 를 사용하지 않고 Rest API 로만 사용할 때 쓸 수 있는 @RestControllerAdvice를 사용합니다.
  • handleDateException 메서든 hibername 관련 에러를 처리합니다.
  • handleCustomException 메서드는 직접 정의한 CustomException 를 사용합니다.
  • Exception 발생 시 넘겨받은 ErrorCode 를 사용해서 사용자에게 보여주는 에러 메시지 를 정의합니다.

3.6 Exception 사용

@RequiredArgsConstructor
@Service
public class MemberService {
    private final MemberRepository memberRepository;

    @Transactional
    public boolean follow(Long memberId) {
        Member currentMember = getCurrentMember();

        // 팔로우할 상대방 정보가 없는 경우
        Member targetMember = memberRepository.findById(memberId)
                .orElseThrow(() -> new CustomException(MEMBER_NOT_FOUND));

        // 자기 자신을 팔로우 하려는 경우
        if (currentMember.equals(targetMember))  {
            throw new CustomException(CANNOT_FOLLOW_MYSELF);
        }

                // code...
    }
}
  • 프로젝트의 일부 코드입니다.
  • 상대방의 Member ID를 입력 받아서 팔로우 하는 기능입니다.
  • Exception 에 담겨지는 ErrorCode만 보고도 어떤 종류의 문제가 발생한 건지 알 수 있습니다.
  • 또한 불필효하게 여러 Exception을 만들지 않고 ErrorCode 만 새로 추가하면 사용가능합니다.
profile
블로그 이전합니다! https://jyyoun1022.tistory.com/
post-custom-banner

0개의 댓글