[Java/Spring] 예외 처리

Subeen·2025년 3월 23일
0

Spring

목록 보기
2/4

@Builder는 빌더 패턴을 적용하는 어노테이션으로 클래스의 필드를 설정할 수 있는 빌더 클래스를 자동으로 생성해줌
이를 통해 객체를 생성할 때 생성자 대신 체이닝 방식으로 필드를 설정할 수 있음

  • 빌더 패턴을 사용하는 경우
UserResponse user = UserResponse.builder() // 빌더 객체를 생성 
        .id("1")
        .name("kim")
        .age(20) // 빌더의 메소드를 통해 필드를 설정 
        .build(); // 최종적으로 객체를 생성하고 반환 
  • 빌더 패턴을 사용하지 않는 경우
// 매개변수 순서가 중요하여 생성자가 많은 인수를 받는 경우에 어려움이 있음 
UserResponse user = new UserResponse("1", "kim", 20);
/**
 * API 응답을 위한 제네릭 클래스
 * API 응답에서 일관된 구조를 제공함 
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Api<T> {
    private String resultCode;  // 응답 코드 (200, 404, 500 등)
    private String resultMessage;  // 응답 메시지 ("OK", "Not Found" 등)
    private T data;  // 응답 데이터
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserResponse { // 응답 객체 
    private String id;
    private String name;
    private Integer age;
}

ExceptionHandler

  • @RestControllerAdvice : REST API에서 발생하는 예외를 전역적으로 처리하는 클래스에 사용하며 예외를 처리하는 메소드가 @ExceptionHandler로 정의되어 클라이언트에게 JSON 응답을 반환
  • @Order : 여러 핸들러나 예외를 처리하는 메소드가 있을 때 실행 순서를 지정할 수 있게 해주는 어노테이션으로 작은 값일수록 우선순위가 높아져 먼저 실행됨
    • @Order를 선언하지 않으면 기본값은 Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)로 설정되어 가장 낮은 우선순위가 적용되어 나중에 실행됨
  • ResponseEntity : HTTP를 구성하고 반환하는데 사용되는 클래스로 응답의 상태 코드, 헤더, body 등을 자유롭게 설정할 수 있음
// 모든 예외를 처리하는 글로벌 예외 핸들러 
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 예측하지 못한 모든 예외를 처리하는 핸들러
    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<Api> exception(Exception e) {
        var response = Api.builder()
                .resultCode(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()))
                .resultMessage(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
                .build();

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(response);
    }
}

// 특정 예외를 처리하는 예외 핸들러 
@RestControllerAdvice
@Order(1) // GlobalExceptionHandler보다 먼저 실행됨
public class RestApiExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(RestApiExceptionHandler.class);

    /**
     * IndexOutOfBoundsException 예외를 처리하는 핸들러
     * 클라이언트에게 200 상태 코드를 반환
     */
    @ExceptionHandler(value = {IndexOutOfBoundsException.class})
    public ResponseEntity<Void> outOfBound(Exception e) {
        return ResponseEntity.status(200).build();
    }

    /**
     * NoSuchElementException 예외를 처리하는 핸들러
     * 클라이언트에게 404 Not Found 응답을 반환
     */
    @ExceptionHandler(value = {NoSuchElementException.class})
    public ResponseEntity<Api> noSuchElement(NoSuchElementException e) {
        var response = Api.builder()
                .resultCode(String.valueOf(HttpStatus.NOT_FOUND.value()))
                .resultMessage(HttpStatus.NOT_FOUND.getReasonPhrase())
                .build();

        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(response);
    }
}

RestController

  • Request
http://localhost:8080/api/user/id/1
  • Response
    • 정상 (resultCode: 200)
    {
      "result_code": "200",
      "result_message": "OK",
      "data": {
        "id": "1",
        "name": "kim",
        "age": 20
      }
    }
    • 일치하는 데이터가 없을 때 (resultCode: 404)
    {
      "result_code": "404",
      "result_message": "Not Found",
      "data": null
    }
    • 예측할 수 없는 예외 발생 (resultCode: 500)
    {
      "result_code": "500",
      "result_message": "Internal Server Error",
      "data": null
    }
/**
 * 사용자 관련 API를 제공하는 컨트롤러
 */
@RestController
@RequestMapping("/api/user")
public class UserApiController {
    private static final List<UserResponse> userList = List.of(
            UserResponse.builder().id("1").name("kim").age(20).build(),
            UserResponse.builder().id("2").name("park").age(30).build()
    );

    /**
     * 사용자 id로 특정 사용자를 조회하는 API
     */
    @GetMapping("/id/{userId}")
    public Api<UserResponse> getUser(@PathVariable String userId) {
    
        // 사용자 조회
        // 해당 id의 사용자가 없으면 예외 발생
        var user = userList.stream().filter(it -> it.getId().equals(userId))
                .findFirst()
                .get();

        // 정상 응답 반환
        return Api.<UserResponse>builder()
                .resultCode(String.valueOf(HttpStatus.OK.value()))
                .resultMessage(HttpStatus.OK.name())
                .data(user)
                .build();
    }
}

안드로이드 앱에서 API 연동을 통해 서버와 데이터를 송수신할 때 주고 받는 데이터에만 중점을 두고 개발을 해서 백엔드에서는 요청에 대한 데이터나 오류를 어떤 방식으로 처리하여 클라이언트에게 응답을 전달하는지 알지 못했다..😅 그래도 Spring에서 예외처리하는 법을 정리하며 조금은 이해할 수 있게 된 것 같다 🐣

profile
개발 공부 기록 🌱

0개의 댓글