-
@Valid 와 @ControllerAdvice로 DTO 예외처리하기Spring 2020. 1. 12. 00:54
@Valid 세팅 및 사용하기 해당편에 이어서
@Valid와 @ControllerAdvice를 이용한 Exception처리를 하려고한다.
@Vailid 사용법에 관한 설명은 생략한다. 위의 링크를 참고하면된다
@Valid는 @ControllerAdvice와 같이 쓰면 궁합이 굉장히 좋다!
회사에서도 물론 같이 쓰고있고 ㅎㅎ
@ControllerAdvice 이란?
@Controller 전역에서 발생할 수 있는 예외를 잡아 처리해주는 annotation이다.
바로 코드로 보자
패키지구조
빨간점의 클래스들만 주의 깊게 보면 된다
Controller
package com.example.validtest.controller;
import com.example.validtest.model.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@Slf4j
@RestController
public class UserController {
@PostMapping(value = "/user")
public String insertUser(@RequestBody @Valid UserDto userDto){
log.info("@ param : {}", userDto);
return "success";
}
}model
package com.example.validtest.model;
import lombok.ToString;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@ToString
public class UserDto {
@NotNull(message = "번호를 입력해주세요")
private long no;
@NotNull(message = "이름을 입력해주세요")
private String name;
@Min(value = 5000, message = "금액은 5000원이상이여야 합니다") // 5000원 보다 같거나, 크면 통과
private long Amt;
}ErrorResponse 클래스는
API에 내려줄 에러 형태를 정하였다
code 변수 : enum을 이용하여 에러코드를 정의한 후 내려줄 것이다
description : 어떠한 에러인지 간단히 넣어줄 것이고 ex) 필수값이 누락되었습니다
detail : 에러의 세부내용을 넣어줄 것이다. ex)이름은 필수값입니다
package com.example.validtest.exception;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class ErrorResponse {
private String code;
private String description;
private String detail;
public ErrorResponse(String code, String description) {
this.code = code;
this.description = description;
}
public ErrorResponse(String code, String description, String detail) {
this.code = code;
this.description = description;
this.detail = detail;
}
}ErrorCode enum은
발생할 에러에 대한 코드, 간단한 에러 설명을 넣어 놓았다.
세부에러내용에 대해서는 DTO 멤버필드에 지정해놓은 message를 보여줄 것이다
package com.example.validtest.exception;
import lombok.Getter;
public enum ErrorCode {
NOT_NULL("ERROR_CODE_0001","필수값이 누락되었습니다")
, MIN_VALUE("ERROR_CODE_0002", "최소값보다 커야 합니다.")
;
@Getter
private String code;
@Getter
private String description;
ErrorCode(String code, String description) {
this.code = code;
this.description = description;
}
}ExceptionController
해당 클래스는 @Controller, @RestController에서 발생한 Exception에 대해서 모두 이곳에서
처리 할 수 있다.
@Valid의 유효성을 통과하지 못하면 , MethodArgumentValidException이 발생한다.
그러면 @ExceptionHandler를 통해서 캐치한 후 Error를 아주 이쁘게 내려주면 되는 것이다.
(@ExceptionHandler에 여러개의 클래스도 지정 할 수 있다.)나는 makeErrorResponse라는 메소드를 만들고 그안에서 validation에 걸린 어노테이션명을 가져와
해당하는 에러를 내려 주었다.
package com.example.validtest.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@ControllerAdvice
public class ExceptionController {
/**
* @valid 유효성체크에 통과하지 못하면 MethodArgumentNotValidException 이 발생한다.
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> methodValidException(MethodArgumentNotValidException e, HttpServletRequest request){
log.warn("MethodArgumentNotValidException 발생!!! url:{}, trace:{}",request.getRequestURI(), e.getStackTrace());
ErrorResponse errorResponse = makeErrorResponse(e.getBindingResult());
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
}
private ErrorResponse makeErrorResponse(BindingResult bindingResult){
String code = "";
String description = "";
String detail = "";
//에러가 있다면
if(bindingResult.hasErrors()){
//DTO에 설정한 meaasge값을 가져온다
detail = bindingResult.getFieldError().getDefaultMessage();
//DTO에 유효성체크를 걸어놓은 어노테이션명을 가져온다.
String bindResultCode = bindingResult.getFieldError().getCode();
switch (bindResultCode){
case "NotNull":
code = ErrorCode.NOT_NULL.getCode();
description = ErrorCode.NOT_NULL.getDescription();
break;
case "Min":
code = ErrorCode.MIN_VALUE.getCode();
description = ErrorCode.MIN_VALUE.getDescription();
break;
}
}
return new ErrorResponse(code, description, detail);
}
}POSTMAN을 이용하여 호출한 결과
'Spring' 카테고리의 다른 글
spring batch 링크모음 (0) 2020.04.21 Gradle 인식못할 때 (0) 2020.04.02 @Valid 세팅 및 사용법 (0) 2019.10.26 MyBatis속성 setting값의 우선순위는 어떻게 될까? (0) 2019.09.15 Gradle annotationProcessor (0) 2019.09.07