API 예외 처리 – @ExceptionHandler
HTML 표시 오류 및 API 오류
HTML 화면을 웹브라우저에 렌더링할 때 오류가 발생했을 때 BasicErrorController를 사용하면 편리합니다.
이 시간
5xx 및 4xx와 관련된 오류 화면을 표시하기만 하면 됩니다. BasicErrorController는 이러한 모든 메커니즘을 구현합니다.
그러나 API는 시스템마다 응답 형식과 사양이 다릅니다.
예외는 단순히 오류 화면을 표시하는 것이 아니라 예외에 따라 다른 정보를 추출해야 할 수 있습니다.
그리고 동일한 예외라도 발생하는 핸들러에 따라 다른 예외 응답을 제공해야 할 수 있습니다.
요컨대, 매우 미세한 제어가 필요합니다.하다.
예를 들어 앞에서 언급한 것처럼 제품 API와 주문 API는 오류가 발생할 때 완전히 다른 응답을 할 수 있습니다.
마지막으로
지금까지 본 BasicErrorController를 사용하거나 HandlerExceptionResolver를 직접 구현합니다.
API 예외 처리는 쉽지 않습니다.
API 예외 처리 문제
처리기 예외 확인자 당신이 기억한다면 모델 및 보기반환되어야 합니다. 이것은 API 응답에 필요하지 않습니다.
API 응답의 경우 HttpServlet 응답답변 데이터를 직접 입력했습니다. 이것은 매우 불편합니다.
Spring 컨트롤러와 비교하는 것은 과거 서블릿을 사용하던 시절로 돌아가는 것과 같습니다.
특정 컨트롤러에서만 발생하는 예외는 별도로 처리하기 어렵습니다.
예를 들어 구성원을 관리하는 컨트롤러에서 런타임 예외 예외 및 제품을 처리하는 컨트롤러에서
일어난 일은 똑같다 런타임 예외 다른 방식으로 예외를 처리하려면 어떻게 해야 합니까?
(HandlerExceptionResolver를 직접 만들고 구현하면 지저분한 코드가 생성됩니다.)
@ExceptionHandler
API 예외 처리 문제를 해결하기 위한 Spring @ExceptionHandler주석 사용
이렇게 하면 예외 작업이 매우 편리해집니다. ExceptionHandlerExceptionResolver~이다
봄이다 ExceptionHandlerExceptionResolver기본적으로 제공됩니다. 기본적으로 제공됩니다. ExceptionResolver 중간
가장 높은 우선순위를 가진다.
실제로 대부분의 API 예외 처리는 이 기능을 사용합니다.
먼저 예를 살펴보자
ErrorResult(예외 응답 객체)
@Data
@AllArgsConstructor
public class ErrorResult {
private String code;
private String message;
}
예외가 발생할 때 API 응답으로 사용되는 개체를 지정합니다.했다
ApiExceptionV2Controller
예외를 처리하는 API, 예외를 발생시키는 API가 있는 컨트롤러
@Slf4j
@RestController
public class ApiExceptionV2Controller {
@ExceptionHandler(IllegalArgumentException.class) //IllegalArgumentException를 처리한다는뜻
public ErrorResult illegalExHandler(IllegalArgumentException e) { //파라미터로 발생한 예외를 받는다.
log.error("(exceptionHandler) ex ", e);
//직접만든 에러 응답 객체 ErrorResult를 반환한다.
return new ErrorResult("BAD", e.getMessage());
}
@GetMapping("/api2/members/{id}") //예외를 발생시키기 위한 API
public MemberDto getMember(@PathVariable String id) {
if (id.equals("ex")) { // ex라는 아아디가 반환되면
throw new RuntimeException("잘못된 사용자"); // 예외를 발생시켲고
}
if (id.equals("bad")) { //bad라고 들어오면
throw new IllegalArgumentException("잘못된 입력값"); //IllegalArgumentException를 발생시킨다.
}
if (id.equals("user-ex")) { //user-ex라고 들어오면
throw new UserException("사용자 오류"); //UserException 발생
}
return new MemberDto(id, "hello" + id); // 아니라면 멤버를 반환해준다.
}
@Data
@AllArgsConstructor
static class MemberDto {
private String memberId;
private String name;
}
}
멤버가되다()는 예외를 발생시키는 API입니다.
불법 ExHandler() 그는 IllegalArgumentException관리방법이다
(불법 ExHandler()는 IllegalArgumentException 또는 모든 하위 클래스를 처리할 수 있습니다.)
@ExceptionHandler 예외 처리 방법
@ExceptionHandler 주석 선언하다,”해당 컨트롤러에서 “처리”하려는 예외를 지정하십시오.할 수 있어요.
( 공통 컨트롤러(@Controller,@RestController) 내부에서 @ExceptionHandler사용)
이 메서드는 “컨트롤러가 예외를 throw할 때” 호출됩니다.하다.
참조 또는 그 하위 클래스로 표시된 모든 예외는 포착될 수 있습니다.
결과

작동 방법

IllegalArgumentException 이 경우 컨트롤러에서 예외가 발생합니다. 하도록 하다 디스패처 서블릿이것 ExceptionResolver전화를 걸 때 예외 처리를 시도하십시오.
최우선 순위 ExceptionHandlerExceptionResolver먼저 전화를 걸다
ExceptionHandlerExceptionResolver처음이다”예외가 발생한 컨트롤러에서” 이제 발생한 예외가 매핑됩니다. @ExceptionHandler있는지 알아보십시오 @ExceptionHandler매핑된 메서드를 호출합니다.

@ExceptionHandler매퍼 메서드가 호출된 후 예외가 처리된 것으로 간주되고 정상적인 흐름이 계속됩니다.
정상적인 응답은 WAS로 가고 정상적인 Http 응답은 200 상태 코드로 처리됩니다.
(예외가 처리되기 때문에 서블릿 컨테이너에서 예외가 끝까지 서블릿으로 가지 않습니다. 따라서 오류 처리를 위해 다시 요청 흐름을 거칠 필요가 없습니다.)
단, 응답코드를 200에서 200이 아닌 다른 상태코드로 변경하고자 하는 경우, @응답상태방법을 추가하여.
@응답상태클래스 수준과 메서드 수준 모두에서 추가할 수 있습니다.
(아래 링크 참조)
https://keeeeeepgoing.tistory.com/181
결과 확인

더 많은 예
@ExceptionHandler첨부된 메서드는 단순히 제네릭 핸들러 메서드(API 메서드)라고 생각하시면 됩니다. 거의 같습니다.
스프링 컨트롤러의 매개변수 응답과 같은 다양한 매개변수 및 응답을 @ExceptionHandler에서 지정할 수 있습니다.
자세한 설정 및 답변은 아래 공식 가이드를 참조하세요.
(https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler-args)
컨트롤러에 비해 파라미터는 적지만 굉장히 다양하니 공식 문서로 가서 참고하자.
(@controller에서 @ExceptionHandler추가된 메소드가 String을 리턴한다면 이는 논리적 뷰 이름으로 판단하여 뷰 리졸버에서 뷰를 찾아 표시하는 것과 같다. 또는 View 개체 자체를 반환할 수 있습니다. (위의 문서를 참조하십시오. API는 예외 처리에 많이 사용되므로 여러 객체를 반환합니다.)
@ExceptionHandler //@ExceptionHandler에 예외를 넣지않고 메소드의 파라미터로 넣어도 알아서 처리해준다.
public ResponseEntity<ErrorResult> userExHanlder(UserException e) { // 일반 핸들러메소드처럼 ResponseEntity로 반환가능하다, 일반 핸들러메소드와 거의동일함
log.error("(exceptionHandler) ex ", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST); //ResponseEntity를 이용하면 상태코드도 지정가능
}
@ExceptionHandler에 예외가 지정되지 않은 경우 해당 메소드 매개변수 예외가 사용됩니다.
여기서는 UserException이 사용됩니다.
결과 확인

더 많은 예
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) { //모든 Exception에 대해
log.error("(exceptionHandler) ex ", e);
return new ErrorResult("EX", "내부오류");
}
이 메서드는 컨트롤러에서 발생하는 모든 예외를 처리합니다. (예외를 매개변수로 사용)
그러나 Bahar의 우선 순위는 항상 세부 사항보다 우선합니다.
IllegalArgumentException이 발생하면 위에서 생성한 예외 처리 방식이 더 자세한 방식이므로 실행됩니다.
그러나 처리되지 않은 예외가 발생하면 예외를 처리하는 이 메서드가 실행됩니다.
(예외는 최상위 인터페이스이므로 처리되지 않은 모든 예외는 여기에서 처리됩니다.)

ResponseEntity를 사용하면 프로그래밍 방식으로 HTTP 응답 코드를 동적으로 변경할 수 있습니다.
위에서 @ResponseStatus 주석을 사용했기 때문에 HTTP 응답 코드를 동적으로 변경할 수 없습니다.
예외 처리 우선순위
Spring의 우선 순위는 항상 세부 사항보다 우선합니다.
예를 들어 부모 클래스와 자식 클래스가 있으며 예외는 다음과 같이 처리됩니다.
@ExceptionHandler(부모예외.class)
public String 부모예외처리()(부모예외 e)
{
}
@ExceptionHandler(자식예외.class)
public String 자식예외처리()(자식예외 e)
{
}
@ExceptionHandler에 지정된 부모 클래스는 자식 클래스도 처리할 수 있습니다.
따라서 자식 예외가 발생하면 부모 예외 handler()와 자식 예외 handler()가 모두 호출됩니다.일어난다
그러나 둘 중 더 장황한 것이 우선하므로 하위 예외 처리()가 호출됩니다.하다.
물론 부모 예외가 호출되면 부모 예외 핸들러()만 호출되므로 부모 예외 핸들러()가 호출된다.하다.
다양한 예외
한 번에 여러 예외를 처리할 수 있습니다. 예를 들면 다음과 같습니다.
@ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
log.info("exception e", e);
}
한계
컨트롤러의 @ExceptionHandler는 매번 발생합니다. 예외를 처리하는 메소드가 있는 경우
하나의 컨트롤러에 일반 코드와 예외 처리 코드가 혼재되어 있으며,
컨트롤러에서 발생한 예외만 처리할 수 있습니다.
이 문제를 해결하려면 @ControllerAdvice 또는 @RestControllerAdvice를 사용하십시오.
API 예외 처리 – @ControllerAdvice
@ExceptionHandler 예외를 깔끔하게 처리할 수 있었습니다.
일반 코드와 예외 처리 코드가 하나의 컨트롤러에 혼합되어 있습니다.
@ControllerAdvice 또는 @RestControllerAdvice 둘을 분리하기 위해.
@ControllerAdvice 여러 컨트롤러 대상
@ExceptionHandler , @InitBinder 기능을 부여하는 역할을 합니다.
@ControllerAdvice에 대상이 지정되지 않으면 모든 컨트롤러에 적용됩니다. (글로벌 적용)
@RestControllerAdvice 하다
@ControllerAdvice 같은 @ResponseBody 추가되었다. @컨트롤러 , @RestController 사이의 차이와 같습니다

ExControllerAdvice
클래스 레벨에 @RestController를 추가한 후 위에서 생성한 예외 메서드를 복사합니다.
컨트롤러 내부에서 예외를 처리할 필요가 없으므로 예외 처리 부분을 제거할 수 있습니다.
@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class) //IllegalArgumentException를 처리한다는뜻
public ErrorResult illegalExHandler(IllegalArgumentException e) { //파라미터로 발생한 예외를 받는다.
log.error("(exceptionHandler) ex ", e);
//직접만든 에러 응답 객체 ErrorResult를 반환한다.
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler //@ExceptionHandler에 예외를 넣지않고 메소드의 파라미터로 넣어도 알아서 처리해준다.
public ResponseEntity<ErrorResult> userExHanlder(UserException e) { // 일반 핸들러메소드처럼 ResponseEntity로 반환가능하다, 일반 핸들러메소드와 거의동일함
log.error("(exceptionHandler) ex ", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST); //ResponseEntity를 이용하면 상태코드도 지정가능
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) { //모든 Exception에 대해
log.error("(exceptionHandler) ex ", e);
return new ErrorResult("EX", "내부오류");
}
}
실행 결과

대상 컨트롤러를 설정하는 방법
@ControllerAdvice는 적용할 컨트롤러도 지정할 수 있습니다.
(@ControllerAdvice에 대상이 지정되지 않은 경우 모든 컨트롤러에 적용(글로벌 적용))
// 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 {}
위의 코드는 공식 문서의 예입니다.
여름 공식 문서의 예에서 볼 수 있듯이
지정된 주석으로 컨트롤러를 정의합니다.할 수 있습니다(주석 속성을 지정하여).
(위에서 RestController라고 표시된 컨트롤러로 지정했습니다.)
특정 패키지를 직접 표시알잖아.
(패키지를 정의하는 경우 패키지와 그 아래의 컨트롤러가 대상이 됩니다.일어난다.)
(이를 구현하면 도메인별로 다른 예외를 관리하거나 원하는 패키지마다 다른 예외를 설정할 수 있습니다.)
그리고 특정 클래스를 지정할 수도 있습니다. 대상 컨트롤러 지정을 생략하면 모든 컨트롤러에 적용됩니다.하다.
(상위 클래스 또는 인터페이스를 상속하고 구현하는 컨트롤러에 대한 예외 처리를 지정할 수 있습니다.
(또는 사용자 지정 컨트롤러 클래스를 포함하여 해당 컨트롤러에 대한 예외 처리를 정의할 수 있습니다.)