예외처리 보충자료

REST API 에러 처리


Produces와 ResponseEntity를 활용한 에러 처리

Produces 우선 처리

  • 클라이언트 요청의 Accept 헤더에 따라 반환 타입을 결정합니다.

    • application/json: JSON 형식으로 반환.

    • text/html: HTML 형식으로 반환.

  • @RequestMapping 또는 @GetMapping에서 produces를 설정하여 반환 형식을 지정할 수 있습니다.

ResponseEntity

  • HTTP 응답 바디에 직접 데이터를 넣어 반환합니다.

  • 상태 코드와 객체를 함께 반환할 수 있습니다.

ResponseEntity 예시

@GetMapping(value = "/example", produces = "application/json")
public ResponseEntity<String> exampleEndpoint() {
    String response = "{\"message\": \"Hello, World!\"}";
    return ResponseEntity.ok(response);
}

서버 에러 설정

application.properties 설정

  • 에러 메시지 출력 옵션:

server.error.include-binding-errors=always
server.error.include-exception=true
  • 위 설정을 통해 바인딩 에러나 예외를 자세히 출력할 수 있습니다.


HandlerExceptionResolver

개요

  • HandlerExceptionResolver를 구현하여 MVC 컨트롤러 밖에서 발생한 예외를 처리할 수 있습니다.

  • 예외를 WAS까지 전달하지 않고 Spring MVC에서 해결합니다.

HandlerExceptionResolver 활용

  1. HTML과 JSON 반환 나누기

    • 클라이언트 요청의 Accept 헤더를 확인하여 반환 형식을 결정합니다.

  2. Spring MVC에서 모든 예외를 처리하고 WAS로 전달하지 않음

    • 에러가 깔끔하게 처리되어 관리가 용이해집니다.

HandlerExceptionResolver 예시

@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(
        HttpServletRequest request, 
        HttpServletResponse response, 
        Object handler, 
        Exception ex
    ) {
        String accept = request.getHeader("Accept");
        if (accept.contains("application/json")) {
            try {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                response.setContentType("application/json");
                response.getWriter().write("{\"error\": \"" + ex.getMessage() + "\"}");
                response.getWriter().flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return new ModelAndView();
        } else {
            ModelAndView modelAndView = new ModelAndView("error");
            modelAndView.addObject("message", ex.getMessage());
            return modelAndView;
        }
    }
}

Spring이 제공하는 ExceptionResolver

  1. ExceptionHandlerExceptionResolver

    • 대부분의 API에서 발생하는 예외를 처리합니다.

  2. ResponseStatusExceptionResolver

    • 예외에 대해 HTTP 상태 코드를 지정합니다.

  3. DefaultHandlerExceptionResolver

    • 스프링 내부에서 발생하는 기본 예외를 처리합니다.


@ResponseStatus를 활용한 예외 처리

@ResponseStatus

  • 특정 예외에 HTTP 상태 코드를 부여할 수 있습니다.

  • messages.properties와 함께 사용자 정의 메시지를 설정할 수 있습니다.

사용 예시

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

messages.properties

resource.not.found=Resource not found.

컨트롤러에서 사용

@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("User with ID " + id + " not found."));
    return ResponseEntity.ok(user);
}

@ExceptionHandler로 예외 처리

개요

  • 컨트롤러 또는 서비스에서 발생하는 예외를 처리합니다.

  • 반환값을 ResponseEntity로 설정하여 클라이언트가 원하는 형식으로 에러를 반환할 수 있습니다.

컨트롤러 단위 예시

@RestController
@RequestMapping("/api/users")
public class UserController {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Map<String, String>> handleResourceNotFound(ResourceNotFoundException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("User not found"));
    }
}

전역 예외 처리

@ControllerAdvice@RestControllerAdvice

  • 모든 컨트롤러 또는 특정 컨트롤러에서 발생한 예외를 처리합니다.

전역 예외 처리 예시

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Map<String, String>> handleResourceNotFound(ResourceNotFoundException ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, String>> handleGenericException(Exception ex) {
        Map<String, String> error = new HashMap<>();
        error.put("error", "An unexpected error occurred: " + ex.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

컨트롤러 적용 범위 지정

특정 컨트롤러 또는 패키지 하위 적용

@ControllerAdvice(assignableTypes = {UserController.class})
public class UserControllerAdvice {
    // 특정 컨트롤러(UserController)만 적용
}

@ControllerAdvice(basePackages = "com.example.api")
public class ApiControllerAdvice {
    // 특정 패키지(com.example.api) 하위 컨트롤러만 적용
}

Last updated