API 예외 처리 - ExceptionResolver 2

Drumj·2022년 11월 29일
0

ExceptionHandlerExceptionResolver

@ExceptionHandler를 사용해보자

@Data
@AllArgsConstructor
public class ErrorResult {

    private String code;
    private String message;
}

우선 데이터 반환을 위해 클래스 하나 만들어 주고.

@Slf4j
@RestController
public class ApiExceptionV2Controller {

    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illeagalExHandler(IllegalArgumentException e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @GetMapping("/api2/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id) {

        if (id.equals("ex")) {
            throw new RuntimeException("잘못된 사용자");
        }
        if (id.equals("bad")) {
            throw new IllegalArgumentException("잘못된 입력 값");
        }
        if (id.equals("user-ex")) {
            throw new UserExceptione("사용자 오류");
        }

        return new MemberDto(id, "hello " + id);
    }

    @Data
    @AllArgsConstructor
    static class MemberDto {
        private String memberId;
        private String name;
    }
}

컨트롤러도 하나 만들어 주고, @ExceptionHandler 도 달아줘따.
수업을 싹~ 따라하긴 했는데..
일단 애노테이션 뒤에 들어와있는 예외는 IllegalArgumentException.class
이렇게 @뒤에 어떤 예외를 처리할깝쇼? 하고 붙여놓으면 해당 컨트롤러에서 붙여놓은 예외가 터지면
(이 예제에서는 IllegalArgumentException 이 되겠다) @ExceptionHandler 메소드가 실행!

그 결과가 아래 사진이다.

여기서 잘 보면 예외가 터졌지만 애노테이션으로 잘 처리가 되어서 정상처리가 된 것 처럼 200 OK가 나오는 것을 볼 수 있다. 이렇게 200 OK가 뜨는게 당연하다고 하시는데 200 이 아닌 다른 status를 보내고 싶으면 @ResponseStatus(HttpStatus.BAD_REQUEST) 를 붙여서 400으로 바꾸면 된다고 한다!


그러면 이렇게!!!! 똑같은 요청인데 400으로 두둥 등장!!!

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illeagalExHandler(IllegalArgumentException e) {
	log.error("[exceptionHandler] ex", e);
	return new ErrorResult("BAD", e.getMessage());
}

최종 코드는 이렇게


다양한 사용법

@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserExceptione e) {
	log.error("[exceptionHandler] ex", e);

	ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
	return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}

이번에는 UserException을 처리해보자!!
오잉??? @ExceptionHandler 여기 뒤에 생략됐는뎁쇼???
생략할 경우에는 메소드 파라미터의 예외로 처리된다고 한다!! 그래서 UserException을 처리할 수 있는 것!

ResponseEntity 를 사용해서 HTTP 메시지 바디에 직접 응답한다. 이렇게 하면 HTTP 응답 코드를 프로그래밍해서 동적으로 변경할 수 있다고 한다.

@ResponseStatus 는 애노테이션이므로 HTTP 응답 코드를 동적으로 변경할 수 없다.

400 bad request 로 오는 것까지 확인 완료!


@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
	log.error("[exceptionHandler] ex", e);
	return new ErrorResult("EX", "내부 오류");
}

이제 남은 나머지(?)예외를 처리! 가장 광범위한 Exception을 가지고 처리하는 것이다.
이거는 500 Internal Server Error로~

스프링의 우선순위는 항상 자세한 것이 먼저라고 한다!

먼저 작성했던 IllegalArgumentExceptionUserExceptione 모두 Exception을 상속받고 있지만 Exception이 가장 부모이므로 우선순위는 가장 낮다!

여튼 요롷고롬 돌리면~~ 500 에러가 뜨는 것도 볼 수 있고 우리가 지정한 EX와 내부 오류 라는 코드와 메시지가 뜨는 것을 확인 할 수 있다!!


단점...?

아~~ 나는 다른 컨트롤러에서도 이 예외처리들을 쓰고 싶은데~~ 또 복붙하기 귀차나~~


@ControllerAdvice / @RestControllerAdvice

한 컨트롤러 안에 예외처리 코드랑 정상 코드가 같이 있으니 복잡하기도 하고, 다른 컨트롤러에 쓰고 싶기도 하다??? 이 애노테이션이면 끝!!!

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illeagalExHandler(IllegalArgumentException e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserExceptione e) {
        log.error("[exceptionHandler] ex", e);

        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }
}

클래스 하나 새로 파서 예외처리 코드를 옮겨담고, @RestControllerAdvice를 붙여줬다!

그리고 컨트롤러 코드는

@Slf4j
@RestController
public class ApiExceptionV2Controller {


    @GetMapping("/api2/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id) {

        if (id.equals("ex")) {
            throw new RuntimeException("잘못된 사용자");
        }
        if (id.equals("bad")) {
            throw new IllegalArgumentException("잘못된 입력 값");
        }
        if (id.equals("user-ex")) {
            throw new UserExceptione("사용자 오류");
        }

        return new MemberDto(id, "hello " + id);
    }

    @Data
    @AllArgsConstructor
    static class MemberDto {
        private String memberId;
        private String name;
    }
}

이렇게 그냥 코드들만!!!

이래도 정상적으로 작동한다!

@RestControllerAdvice를 사용한 이유는 API스타일이라서 그런것

@RestControllerAdvice와 @ControllerAdvice의 차이는 @ResponseBody의 유무
@RestController와 @Controller의 차이와 똑같다!


@ControllerAdvice의 적용 대상은?

대상을 지정하지 않으면 모든 컨트롤러에 적용된다고 한다!

그리고 그 컨트롤러들에 @ExceptionHandler , @InitBinder 기능을
부여해주는 역할을 한다.

대상을 지정하는 방식으로는

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
특정 애노테이션이 있는 컨트롤러를 지정

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
특정 패키지를 지정

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class,
AbstractController.class})
public class ExampleAdvice3 {}
특정 클래스를 지정

이런 방식들이 있다고 한다.


예외처리 끝?

우선 강의는 끝났는데... 강의 한 번 싹 듣고 정리하고 하니까 이해는 더 잘 되는 것 같....기도??

이걸 프로젝트 만들면서 적용도 해봐야 하는데
프로젝트는 언제쯤 만들어볼 수 있을까.. 아직 가야할 길이 멀다
아자아자!!!

0개의 댓글