[Spring] Spring Boot 예외 처리 설명 & 예제 (Kotlin)
- ETC../Spring
- 2021. 3. 17.
728x90
Exception Annotation
- @ControllerAdvice : Global 예외 처리 및 특정 pakage / Controller 예외 처리
- @ExceptionHandler : 특정 Controller의 예외 처리
GlobalControllerAdvice
//@RestControllerAdvice(basePackageClasses = [ExceptionApiController::class]) -> Target 설정 가능
//@RestControllerAdvice // RestController의 Exception이 이 컨트롤러를 통하게 됨(Global : 괄호X)
class GlobalControllerAdvice { // 특정 예외를 잡겠다고 지정
@ExceptionHandler(value = [RuntimeException::class]) //Runtime Error 터지면 아무거나 다잡음 -> 지정하지 않았는데 처리 되었음 -> Advice 명시 해주거나, 정말 표준적인 것만 처리
fun exception(e: RuntimeException): String {
return "Server Error"
}
// @ExceptionHandler(value = [IndexOutOfBoundsException::class])
// fun indexOutOfBoundsException(e: IndexOutOfBoundsException): ResponseEntity<String> { // 200 OK, Catch 됨
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Index Error")
// }
// 고객사를 위한 익셉션.. 등등을 글로벌 하게 잡아 줄 수 있
// 특정 클래스 , 특정 함수 등 특정할 수 도 있다.
}
@RestController
@RequestMapping("/api/exception")
@Validated
class ExceptionApiController { // 연습용 Api, 실무는 X
@GetMapping("/hello")
fun hello() {
val list = mutableListOf<String>()
val temp = list[0]
}
@GetMapping("")
fun get(
@NotBlank
@Size(min = 2, max = 6)
@RequestParam name: String,
@Min(10)
@RequestParam age: Int
): String {
//통과된 경우만 -> print(Validated)
println(name)
println(age)
return name + " " + age
}
@PostMapping("")
fun post(@Valid @RequestBody userRequest: UserRequest): UserRequest { // bindingResult: BindingResult -> 결과 받아준다, Validation 사용하기 때문에 삭제
println(userRequest)
return userRequest
}
@ExceptionHandler(value = [MethodArgumentNotValidException::class]) // createdAt -> 형식 오류
fun methodArgumentNotValidException(
e: MethodArgumentNotValidException,
request: HttpServletRequest
): ResponseEntity<ErrorResponse> { //매개 변수 설정
val errors = mutableListOf<Error>()
e.bindingResult.allErrors.forEach { errorObject ->
val error = Error().apply {
this.field = (errorObject as FieldError).field // 형변환 field Name 불러 올 수 있음
this.message = errorObject.defaultMessage
this.value = errorObject.rejectedValue //rejected Value
}
errors.add(error)
}
// 2. ErrorResponse
val errorResponse = ErrorResponse().apply {
this.resultCode = "FAIL"
this.httpStatus = HttpStatus.BAD_REQUEST.value().toString()
this.httpMethod = request.method
this.message = "요청에 에러가 발생하였습니다."
this.path = request.requestURI.toString() // 현재 request를 찾아서 주입해 줌
this.timestamp = LocalDateTime.now()
this.errors = errors
}
// 3. Return -> ResponseEntity
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse)
}
@ExceptionHandler(value = [ConstraintViolationException::class]) // 어떤 에러를 가지는지 가져올 수 있음, 이번에는 GET, requestParam Error(Validation)
fun constraintVaolationException(
e: ConstraintViolationException,
request: HttpServletRequest
): ResponseEntity<ErrorResponse> {
// 1. 에러 분석
val errors = mutableListOf<Error>() // 에러 하나하나 넣어준다 : forEach
e.constraintViolations.forEach {
val error = Error().apply {
this.field = it.propertyPath.last().name // 마지막에 변수 이름 들어 있음
this.message = it.message
this.value = it.invalidValue
}
errors.add(error)
}
// 2. ErrorResponse
val errorResponse = ErrorResponse().apply {
this.resultCode = "FAIL"
this.httpStatus = HttpStatus.BAD_REQUEST.value().toString()
this.httpMethod = request.method
this.message = "요청에 에러가 발생하였습니다."
this.path = request.requestURI.toString() // 현재 request를 찾아서 주입해 줌
this.timestamp = LocalDateTime.now()
this.errors = errors
}
// 3. Return -> ResponseEntity
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse)
}
// if (true) {
// throw RuntimeException("강제 exception 발생") //hello method 실행되면 바로 Exception -> java.lang.RuntimeException: 강제 exception 발생
// }
// 컨트롤러 내부에 Exception Handler가 있다면 Advice타지 않고 이 곳을 탄다 , 각 장단점이 있다.
// 클래스 내부의 경우는 해당 컨트롤러 안에서만 일어나는 예외 한번에 처리 가능
// 너무 예외가 많아지면 가독성이 떨어지는 단점이 있다.
@ExceptionHandler(value = [IndexOutOfBoundsException::class])
fun indexOutOfBoundsException(e: IndexOutOfBoundsException): ResponseEntity<String> { // 200 OK, Catch 됨
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Index Error")
}
}
결과 화면 예시(Response설계..)
data class ErrorResponse(
@field:JsonProperty("result_code") //snake pattern 으로 변환(실제 Response)에서는
var resultCode: String? = null,
@field:JsonProperty("http_status")
var httpStatus: String? = null,
@field:JsonProperty("http_method")
var httpMethod: String? = null,
var message: String? = null,
var path: String? = null,
var timestamp: LocalDateTime? = null,
var errors: MutableList<Error>? = mutableListOf()
)
data class Error(
var field: String? = null,
var message: String? = null,
var value: Any? = null
)
GitHub
GitHub - SeungyeonHwang/restapi-crud-test: RESTapi Test with Kotlin and Spring Boot
728x90
'ETC.. > Spring' 카테고리의 다른 글
[Spring] Spring Boot로 ToDoApp만들기(투두 앱) (1) (0) | 2021.04.05 |
---|---|
[Spring] Spring Boot Junit 단위 테스트 설명 & 예제 (Kotlin) (0) | 2021.04.04 |
[Spring] SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet (0) | 2021.03.16 |
[Spring] Spring Boot Validation 설명 & 예제 (Kotlin) (0) | 2021.03.12 |
[Spring] Java를 이용한 간단한 CRUD API만들기 (0) | 2021.01.29 |