예외 처리 - API

JeongHoHyun·2025년 2월 14일

Spring MVC

목록 보기
19/21

API 예외처리

방법 1. API응답 추가

ErrorPageController

@RequestMapping(value = "/500", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response) {
        log.info("API errorPage 500");

        Map<String, Object> result = new HashMap<>();
        Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);
        result.put("status", request.getAttribute(ERROR_STATUS_CODE));
        result.put("message", ex.getMessage());

        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        return new ResponseEntity<>(result, HttpStatus.valueOf(statusCode));
    }
  • produces = MediaType.APPLICATION_JSON_VALUE를 통해 HTTP Header의 Accept값이 application/json일 때 해당 메서드가 호출된다.
  • 응답 데이터를 위해 Map을 만들고, status, mesaage에 값을 할당 했다.
    • Jackson라이브러리는 Map을 JSON 구조로 변환할 수 있다.
  • ResponseEntity로 응답하기 때문에 메시지 컨버터가 동작하여 JSON이 반환된다.

방법 2. 스프링 부트 기본 오류처리

스프링 부트가 제공하는 BasicErrorController에 기본 API 예외처리도 제공한다.

{
  "timestamp": "2021-04-28T00:00:00.000+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "exception": "java.lang.RuntimeException",
  "trace": "java.lang.RuntimeException: 잘못된 사용자\n\tat hello.exception.web.api.ApiExceptionController.getMember(ApiExceptionController.java:19...,"
  "message": "잘못된 사용자"
  ,
  "path": "/api/members/ex"
}

HandlerExceptionResolver

  • 컨트롤러 밖으로 던저진 예외를 해결하고, 동장 방식을 변경하고싶을때 사용한다.
  • HandlerExceptionResolver 인터페이스를 상속받아 개발한다.
public interface HandlerExceptionResolver {
	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
		Object handler, Exception ex);
}
  • hanlder : 핸들러(컨트롤러) 정보
  • Exception ex : 핸들러(컨트롤러)에서 발생한 예외

MyHandlerExceptionResolve

package hello.exception.resolver;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import java.io.IOException;

@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.info("call resolver", ex);
        try {
            if (ex instanceof IllegalArgumentException) {
                log.info("IllegalArgumentException resolver to 400");
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
                return new ModelAndView();
            }
        } catch (IOException e) {
            log.error("resolver ex", e);
            e.printStackTrace();
        }

        return null;
    }
}
  • ExceptionResolverModelAndView를 반환하는 이유는 try-catch를 하듯이 Exception을 처리하여 정상흐름처럼이 변경하는 것이 목적이기 때문이다.

반환 값에 따른 동작 방식

  • 빈 ModelAndView : 빈 ModelAndView를 반환하면 뷰를 렌더링 하지 않고, 정상 흐름으로 서블릿이 리턴.
  • ModelAndView 지정 : ModelAndViewView, Model등의 정보를 지정해서 반환하면 뷰를 렌더링.
  • null : 다음 ExceptionResolver를 찾아서 실행. 만약 처리할 수 있는 ExceptionResolver가 없으면 예외처리가 안되고, 기존에 발생한 예외를 서블릿 밖으로 던진다.

ExceptionResolver 활용

  • 예외 상태 코드 변환
    • 예외를 response.sendError(xxx)호출로 변경해서 서빌릿에서 상태 코드에 따른 오류처리를 하도록 위임.
    • 이후 WAS는 서블릿 오류페이지를 찾아서 내부 호출 (ex:기본설정한 /error호출)
  • 뷰 템플릿 처리
    • ModelAndView에 값을 채워 예외에 따른 오류하면 뷰 렌더링
  • API 응답 처리
    • response.getWriter().println("hello")처럼 HTTP 응답 바이데 직접 데이터를 넣어 처리 가능. (JSON응답시 API응답 처리 가능)

WebConfig 설정

  • ExceptionResolver 사용을 위해 WebMvcConfigurer 통해 등록.
  • extendHandlerExceptionResolvers 를 통해 등록.
    • configureHandlerExceptionResolvers(..) 를 사용하면 스프링이 기본으로 등록하는 ExceptionResolver 가 제거
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new MyHandlerExceptionResolver());
        resolvers.add(new UserHandlerExceptionResolver());
    }
    .
    .
    .
 }

⭐️ 방법 3. @ExceptionHandler

스프링이 제공하는 API예외 처리를 위한 애노테이션이다.
스프링은 ExceptionHandlerExceptionResolver를 기본제공하고, ExceptionResolver중에서 우선순위도 가장 높다.

ErrorResult

  • API응답으로 사용하기 위한 객체 정의
package hello.exception.exhandler;

import lombok.AllArgsConstructor;
import lombok.Data;

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

@ExceptionHandler 예외처리 방법

  • @ExceptionHandler 애노테이션을 설정하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면된다.
  • 해당 컨트롤러에서 예외가 발생하면 이 메서드가 호출된다.
  • 지정한 예외 또는 그 예외의 자식클래스는 모두 잡을 수 있다.
	@ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalArgumentException(IllegalArgumentException e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException 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", "내부 오류");
    }

@ControllerAdvice

  • 정상코드와 예외처리 코드를 분리하는 애노테이션이다.
  • @ControllerAdvice, @RestControllerAdvice를 사용하면 분리할 수 있다.

예제 - @RestControllerAdvice

package hello.exception.exhandler.davice;

import hello.exception.exception.UserException;
import hello.exception.exhandler.ErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {

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

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException 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", "내부 오류");
    }
}
  • 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler, @InitBinder기능을 부여해주는 역할을 한다.
  • 대상을 지정하지 않으면 모든 컨트롤러에 적용된다.

대상 지정 방법

// 애노테이션 지정
@ControllerAdvice(annotations = RestController.class)

// 패키지 지정
@ControllerAdvice("org.example.controllers")

// 클래스 지정
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
profile
Java Back-End 2022.11.01 💻~ing

0개의 댓글