SPRING MVC - 예외 처리(2) : API

Sungjin·2021년 8월 12일
1

Spring

목록 보기
14/23
post-thumbnail

🎯 API 예외 처리

예외 처리와 오류페이지가 궁금하시다면! 🙋‍♀️

단순히 에러 및 오류가 났을 때는 BasicErrorController가 제공하는 기능을 사용하여 사용자에게 해당 번호대 오류 페이지를 보여주기만 하면 되었습니다.

하지만 API의 경우 사용자에게 각각 상황에 맞는 오류에 대하여 JSON으로 응답을 해야하기 때문에 HTML페이지를 보여주는 것 이상으로 세밀하게 JSON을 제공해야 합니다.

🚀 스프링 부트에서 기본으로 제공하는 오류 처리

BasicErrorController는 오류 페이지 뿐만 아니라 API 예외 처리 기능도 제공합니다!

잠깐 BasicErrorController의 코드 일부를 먼저 보시죠!


	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}

여기서 중요히 보실 부분은 RequestMapping에 produces입니다.

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) 
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request)

이렇게 두 부분으로 나뉘어 진 것을 보실 수 있습니다.

errorHtml()의 produces = MediaType.TEXT_HTML_VALUE 는 클라이언트의 requset Accept Header값이 text/html일 때를 의미합니다.
즉 text/html형식으로 요청이 오면 오류 페이지를 제공한다고 보시면 됩니다.

그리고, error()는 text/html이외의 요청에 호출 되고 반환 값은 코드에서 보시는 데로 ResponseEntoty로 Json 데이터를 반환하게 됩니다.

BasicErrorController는 Json 메시지도 변경할 수 있다고 합니다. 하지만 물론 BasicErrorControlelr는 오류 페이지를 제공하는데는 매우 효율적이지만 API에서 일어나는 오류에 대한 처리는 정말 세세하게 이뤄져야 하므로 잘 사용하지는 않는다고 합니다( ex IllegalArgumentException의 경우 입력값을 잘못 넣은 사용자의 잘못이라고 볼 수 있는데 에러 코드는 500으로 나감. 이 에러코드를 4xx대의 코드로 바꿔서 보내줘야한다.. 이런!).

그 대신 HandlerExceptionResolver를 사용한다고 합니다!

🚀 ExceptionResolver - 스프링 제공

  • 기본적으로 제공하는 ExceptionResolver

    1. ExceptionHandlerExceptioinResolver
      @ExceptionHandler annotaion처리

    2. ResponseStatusExceptionResolver

      @ResponseStatus(value=HttpStatus.BAD_REQUEST) ... Http 상태 코드를 지정

      @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
      public class BadRequestException extends IllegalArgumentException 

      이런 식으로 사용 가능 합니다. BadRequestException 예외가 발생하게 되면 오류 코드를 HttpStatus.BAD_REQUEST로 바꾸고 메시지를 reason에 담아 응답하게 됩니다.

    3. DefaultHandlerExceptionResolver

      스프링 내부에서 발생하는 예외를 해결 합니다.
      ex) TypeMismatchException 과 같은 경우 대부분의 클라이언트 잘못. 이와 같은 경우 스프링에서 InternalServerError가 아닌 BAD_REQUEST의 에러로 반환

    우선 순위 순 입니다.

상황 상황 마다 세밀한 에러 처리를 하고 싶을 때 ExceptionHandlerExceptioinResolver를 사용합니다.

CODE 🙋‍♀️를 보시죠~

ErrorResult

@Data
@AllArgsConstructor
public class ErrorResult {
    private String code;
    private String message;
}

ApiExceptionController

@Slf4j
@RestController
public class ApiExceptionController {

    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult badHandler(IllegalArgumentException e){
        return new ErrorResult("BAD",e.getMessage() );
    }
    
    @GetMapping("/test/{code}")
    public String test(@PathVariable("code")String code){
        if(code.equals("bad")){
            throw new IllegalArgumentException("잘못된 값");
        }
        return "ok";
    }
}

http://localhost:8080/test/bad 로 요청이 오면
ErrorResult에 "BAD"라는 code와 "잘못된 값"이라는 message가 담겨서 반환 될 것 입니다!
그외에 다른 요청은 "OK"를 반환하게 될 것입니다.

한번 확인 해보죠!

이번에는 IllegalArgument를 상속 받은 예외 클래스를 추가한 후 실행해 봅시다!

UserException

public class UserException extends IllegalArgumentException{
    public UserException() {
        super();
    }

    public UserException(String s) {
        super(s);
    }

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

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

ApiExceptionController

@Slf4j
@RestController
public class ApiExceptionController {

    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult badHandler(IllegalArgumentException e){
        return new ErrorResult("BAD",e.getMessage() );
    }
    
    @ExceptionHandler(UserException.class)
    public ErrorResult userHandler(UserException e){
        return new ErrorResult("USER-ERROR",e.getMessage());
    }
    
    @GetMapping("/test/{code}")
    public String test(@PathVariable("code")String code){
        if(code.equals("bad")){
            throw new IllegalArgumentException("잘못된 값");
        }
        if(code.equals("user-bad")){
            throw new UserException("USER 잘못!");
        }
        return "ok";
    }
}

http://localhost:8080/test/user-bad 로 요청

결과

보셨다시피 해당 컨트롤러에서 예외가 발생하면 @ExceptionHandler가 지정된 메서드가 실행 됩니다.
IllegalArgumentException을 상속받은 UserException을 처리한 것을 보셨다 시피 다양하게 상황 상황 맞게 예외를 처리할 수 있는 것을 보실 수 있습니다!

👍 TIP

매번 Controller마다 @ExceptionHandler를 지정하기 귀찮죠!
그래서 @ControllerAdvice & @RestControllerAdvice 기능을 제공한다고 합니다!
이들은 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler 및 @InitBinder 기능을 부여 해주며 대상을 지정해주지 않으면 모든 컨트롤러에 지정된다고 합니다!

ex)

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {

	@ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult badHandler(IllegalArgumentException e){
        return new ErrorResult("BAD",e.getMessage() );
    }
    
    @ExceptionHandler(UserException.class)
    public ErrorResult userHandler(UserException e){
        return new ErrorResult("USER-ERROR",e.getMessage());
    }
}

이상으로 포스팅을 마치겠습니다. 감사합니다 :) 👋

이 글은 인프런 김영한님의 '스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술'을 수강하고 작성합니다.
출처:https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

profile
WEB STUDY & etc.. HELLO!

0개의 댓글