SpringBoot에서 Exception

LeeKyoungChang·2023년 4월 18일
0
post-thumbnail

예외가 발생했을 때, 종류별로 처리할 수 있으면 좋다.

이를 대비해 Exception 디렉터리를 생성해, 각각의 오류마다 해당 결과를 전달해준다.

kotlin 기반으로 작성

 

✔️ Exception 계층

error

exception1

 

 

📚 1. SpringBoot에서 @ExceptionHandler

📖 A. @ExceptionHandler 사용방법

@ExceptionHandler : Controller 계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능 (Service, Repository에서 발생하는 에러는 제외한다.)


@Controller 클래스안에서

  

~

  

@ExceptionHandler

fun handle(ex: IOException): ResponseEntity<String>{

}

  

~
  • @Controller로 선언된 클래스 안에서 @ExceptionHandler 어노테이션으로 메서드 안에서 발생할 수 있는 에러를 처리할 수 있다.

 

✔️ 여러 개의 Exception 처리하는 방법

@ExceptionHandler 의 value 값으로 어떤 Exception을 처리할 것인지 넘겨줄 수 있다.

만약, value를 설정하지 않으면 모든 Exception을 잡게 되기 때문에, Exception을 구체적으로 적어주는 것이 좋다.


        @Controller 안이다.

        @ExceptionHandler(value = [FileSystemException::class, RemoteException::class])

    fun handle(ex: IOException): ResponseEntity<String>{

  

    }
  • 여러 개의 Exception을 잡아줄 때는, @ExceptionHandler(value = [Exception1::class, Exception2::class]) 와 같이 명시해주면 된다.

 

 

📚 2. ControllerAdvice

📖 A. @ControllerAdvice에서 @ExceptionHandler 사용

@ControllerAdvice : @Controllerhandler에서 발생하는 에러들을 모두 잡아준다.

@ControllerAdvice 안에서 @ExceptionHandler 를 사용하여 에러를 잡을 수 있다.

(ControllerAdvice는 java로 함)


@ControllerAdvice

public class ExceptionHandlers {

  

    @ExceptionHandler(FileNotFoundException.class)

    public ResponseEntity handleFileException() {

        return new ResponseEntity(HttpStatus.BAD_REQUEST);

    }

  

}

 

✔️ 범위 설정

@ControllerAdvice 는 모든 에러를 잡아주기 때문에, 일부 에러만 처리하고 싶을 경우에는 따로 설정을 해주면 된다.

(1) 어노테이션

(2) basePackages (+basepackagesClasses)

(3) assignableTypes


// 1.

@ControllerAdvice(annotations = RestController.class)

public class ExampleAdvice1 {}

  

// 2.

@ControllerAdvice("org.example.controllers")

public class ExampleAdvice2 {}

  

// 3.

@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})

public class ExampleAdvice3 {}
  • basePackages : 탐색 패키지 지정

  • org.example.controllers : 패키지, 하위 패키지까지 모두 탐색

  • basePackagesClasses : 탐색 클래스 지정, 클래스의 맨 위에 있는 package부터 시작

 

💡 참고
어노테이션, 베이스 패키지 등 설정자들은 runtime시 수행되기 때문에 너무 많은 설정자들을 사용하면 성능이 떨어질 수 있다.

 

 

📚 3. RestControllerAdvice

@RestControllerAdvice : @ControllerAdvice@ResponseBody 을 가지고 있다.

@Controller 처럼 작동하며 @ResponseBody 를 통해 객체를 리턴할 수 있다.

 

✔️ @RestControllerAdvice


@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@ControllerAdvice

@ResponseBody

public @interface RestControllerAdvice {

    // ...  

}

 

📖 A. @ControllerAdvice vs @RestControllerAdvice

@ControllerAdvice : @Component 어노테이션을 가지고 있어 컴포넌트 스캔을 통해 스프링 빈으로 등록된다.

@RestControllerAdvice : @Controlleradvice@ResponseBody 어노테이션으로 이루어져 있고 HTML 뷰 보다는 ResponseBody로 값을 리턴할 수 있다.

 

 

📚 4. 프로젝트 리팩토링

현재 게시판(Board) id를 찾던 중 BoardResourceNotFoundException이 발생

✔ 현재 Exception 구조

exception

 

 

✏ 2. @RestControllerAdvice, GlobalExceptionHandler


@RestControllerAdvice

class GlobalExceptionHandler {

    /* 설명

    RESTful API에서 발생하는 예외 처리를 담당

    예외 처리를 통해 클라이언트에게 적절한 에러 응답을 보내는 것이 중요하다.

    Spring Framework에서 예외 처리를 담당하는 @RestControllerAdvice 클래스인 GlobalExceptionHandler

    (1) handleCustomException

    - CustomException 발생 시 호출되는 메소드

    - CustomException에서 발생한 ErrorCode 객체를 이용하여 ErrorResponse 객체를 생성하여 반환

  

    (2) handleBindException

    - BindException 발생 시 호출되는 메소드

    - BindException에서 발생한 BindingResult 객체를 이용하여 ErrorResponse 객체를 생성하여 반환

    */

  

    private val logger = KotlinLogging.logger {}

    @ExceptionHandler(value = [CustomException::class])

    protected fun handleCustomException(e: CustomException): ResponseEntity<ErrorResponse?>? {

        return ErrorResponse.toResponseEntity(e.getErrorCode())

    }

}
  • CustomException의 하위 Exception들도 handleCustomException에 포함된다.

e: CustomException

CustomException 자식 계층에 BoardResourceNotFoundException 이 있다.

 

 

✏ 3. BoardErrorCode, BoardResourceNotFoundException

%EB%8F%99%EC%9E%91%EA%B3%BC%EC%A0%951

 

✔ BoardErrorCode


import org.springframework.http.HttpStatus

import study.kotlin.boardtoyproject.common.exception.ErrorCode

  

enum class BoardErrorCode(override val status:HttpStatus, override val message: String) : ErrorCode{

  

    // 400 BAD REQUEST

    INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "파라미터 값을 확인해주세요."),

  

    // 404 NOT FOUND

    BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 방 번호 입니다."),

    YOUR_NOT_MEMBER(HttpStatus.NOT_FOUND, "회원이 아닙니다."),

    RESOURCE_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "현재 요청사항을 찾지 못했습니다. id를 다시 확인해주세요."),

  

    SAVED_BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "저장된 게시판이 아닙니다."),

  

    // 409 CONFLICT 중복된 리소스

    ALREADY_SAVED_BOARD(HttpStatus.CONFLICT, "이미 저장된 방입니다."),

  

    // 500 INTERNAL SERVER ERROR

    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러가 발생했습니다. 서버에 연락주세요.")

  

}
  • errorCode를 선언한 enum

 

✔ CustomException


open class CustomException(private val errorCode: ErrorCode) : RuntimeException() {

    // 404, 409 에러들은 따로 잡아주어야 하기 때문에 필요한 클래스

  

    fun getErrorCode(): ErrorCode{

        return errorCode

    }

}
  • 404, 409 에러들은 따로 잡아주어야 하기 때문에 필요한 클래스

 

✔ BoardResourceNotFoundException


import study.kotlin.boardtoyproject.common.exception.CustomException

import study.kotlin.boardtoyproject.common.exception.ErrorCode

  

// 현재 요청사항을 찾지 못했습니다. id를 다시 확인해주세요.

class BoardResourceNotFoundException private constructor(errorCode: ErrorCode) : CustomException(errorCode){

    companion object {

        val CODE = BoardErrorCode.RESOURCE_NOT_FOUND_EXCEPTION

    }

    constructor() : this(CODE)

}
  • companion object : static 전역 변수 선언

  • CODE : enum에서 생성한 변수이다.

  • constructor , 보조 생성자(Secondary Constructor)를 통해 CODE를 private constructor(errorCode: ErrorCode)CustomException constructor으로 넘겨준다.

  • BoardResourceNotFoundException는 CustomException의 자식 계층이다.

 

 

✏ 4. ErrorResponse

%EB%8F%99%EC%9E%91%EA%B3%BC%EC%A0%952


data class ErrorResponse(

        val timestamp: LocalDateTime = LocalDateTime.now(),

        val status: Int,

        val message: String?,

        val errors: List<FieldError>

){

  

    // static

    companion object {

        // 메서드

        fun toResponseEntity(errorCode: ErrorCode): ResponseEntity<ErrorResponse?> {

            return ResponseEntity.status(errorCode.status).body(ErrorResponse(errorCode))

        }

        }

  

        constructor(errorCode: ErrorCode) : this(

            status = errorCode.status.value(),

            message = errorCode.message,

            errors = emptyList()

    )

}

  
  • errorCode : status(상태), message(메시지) 를 담아서 toResponseEntity를 호출했다.

  • bodyErrorResponse(errorCode) 보조 생성자가 실행된다.

 

보조 생성자


        constructor(errorCode: ErrorCode) : this(

            status = errorCode.status.value(),

            message = errorCode.message,

            errors = emptyList()

    )

 

결국 status와 body에 각각의 데이터를 담아서 ResponseEntity가 반환된다.

사용자는 이것을 보고 어떤 오류인지 쉽게 판단할 수 있게 된다.

 

 


참고 : https://velog.io/@kiiiyeon/스프링-ExceptionHandler를-통한-예외처리

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글