Burninghering's Blog
article thumbnail
Published 2022. 6. 13. 23:01
Validation 모범 사례 Spring

Global하게 잡아주던 예외처리를

한 클래스에만 한정되어 지정해주게 함

<java />
package com.example.exception.advice; import com.example.exception.controller.ApiController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice(basePackageClasses = ApiController.class) //ApiController에서만 작동하게 된다 public class ApiControllerAdvice { ////ApiController에서만 작동하게 되니까 Global에서 Api로 바꿔줌 @ExceptionHandler(value = Exception.class) public ResponseEntity exception(Exception e){ System.out.println(e.getClass().getName()); //어떤 클래스에서 에러가 났는지 확인! return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); } @ExceptionHandler(value= MethodArgumentNotValidException.class) public ResponseEntity MethodArgumentNotValidException(MethodArgumentNotValidException e){ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } }

 

이전 강의에서는 Post 예외처리만 해주었는데,

Get 요청으로도 해보자

NullPointerException이다

 

ApiController 코드 바꿔주자

<java />
package com.example.exception.controller; import com.example.exception.dto.User; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @RestController @RequestMapping("/api/user") @Validated public class ApiController { @GetMapping("") public User get( @Size(min=2) @RequestParam String name, @NotNull @Min(1) @RequestParam Integer age){ User user = new User(); user.setName(name); user.setAge(age); return user; } @PostMapping("") public User post(@Valid @RequestBody User user){ System.out.println(user); return user; } // @ExceptionHandler(value= MethodArgumentNotValidException.class) // public ResponseEntity MethodArgumentNotValidException(MethodArgumentNotValidException e){ // // System.out.println("api controller"); // return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); // } }

 

Get 요청 보냈더니 아래와 같은 에러가 뜬다

<java />
org.springframework.web.bind.MissingServletRequestParameterException

 

인자를 넣어주면 해결되는 오류임!

 


또 다른 예제

제한을 바꿔보자!

<code />
@GetMapping("") public User get( @Size(min=2) @RequestParam String name, @NotNull @Min(1) @RequestParam Integer age){ User user = new User(); user.setName(name); user.setAge(age); return user; }

<java />
javax.validation.ConstraintViolationException

 

ApiControllerAdvice.java에 

위와 같은 에러 잡는 코드 추가

<code />
@ExceptionHandler(value= ConstraintViolationException.class) public ResponseEntity constraintViolationException(ConstraintViolationException e){ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); }

값을 아무것도 집어넣지 않았을 때 

생기는 에러 

<java />
org.springframework.web.bind.MissingServletRequestParameterException

ApiControllerAdvice.java에

위와 같은 에러 잡는 코드 추가

 

<java />
@ExceptionHandler(value= MissingServletRequestParameterException.class) public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); }

Post 요청에 값 안넣었을 때

오류 잡기

<code />
@ExceptionHandler(value= MethodArgumentNotValidException.class) //인자가 없을 때 발생하는 에러 잡기 public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){ BindingResult bindingResult = e.getBindingResult(); bindingResult.getAllErrors().forEach(error->{ FieldError field = (FieldError) error; //형변환 String fieldName=field.getField(); String message = field.getDefaultMessage(); String value = field.getRejectedValue().toString(); System.out.println("---------------"); System.out.println(fieldName); System.out.println(message); System.out.println(value); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); }

 

오른쪽 콘솔과 같이 에러 메시지가 뜬다!

 

Get 요청도 보냈을 때

BODY에 에러문이 뜬다

 

여태까지의 총 코드

<code />
package com.example.exception.advice; import com.example.exception.controller.ApiController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolationException; import java.lang.reflect.Field; @RestControllerAdvice(basePackageClasses = ApiController.class) //ApiController에서만 작동하게 된다 public class ApiControllerAdvice { ////ApiController에서만 작동하게 되니까 Global에서 Api로 바꿔줌 @ExceptionHandler(value = Exception.class) public ResponseEntity exception(Exception e){ System.out.println(e.getClass().getName()); //어떤 클래스에서 에러가 났는지 확인! return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); } @ExceptionHandler(value= MethodArgumentNotValidException.class) //인자가 없을 때 발생하는 에러 잡기 public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){ BindingResult bindingResult = e.getBindingResult(); bindingResult.getAllErrors().forEach(error->{ FieldError field = (FieldError) error; //형변환 String fieldName=field.getField(); String message = field.getDefaultMessage(); String value = field.getRejectedValue().toString(); System.out.println("---------------"); System.out.println(fieldName); System.out.println(message); System.out.println(value); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } @ExceptionHandler(value= ConstraintViolationException.class) public ResponseEntity constraintViolationException(ConstraintViolationException e){ //어떠한 필드가 잘못되었을때의 정보를 담고 있음 e.getConstraintViolations().forEach(error ->{ System.out.println(error); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } @ExceptionHandler(value= MissingServletRequestParameterException.class) public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } }

Get요청에 값에 아무것도 넣지 않고 보냈을 때

에러 메시지가 안떠서 어느 함수를 거쳐서 결과를 냈는지

빨간색으로 코드를 표시하고 디버깅해보았다

 

맨 아래

<code />
missingServletRequestParameterException

함수를 거친 것이다!

 

그래서 코드를 추가해주었다

<code />
@ExceptionHandler(value= MissingServletRequestParameterException.class) public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){ String fieldName=e.getParameterName(); String fieldType=e.getParameterType(); String invalidValue=e.getMessage(); System.out.println(fieldName); System.out.println(fieldType); System.out.println(invalidValue); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); }

 

결과

<java />
age Integer Required request parameter 'age' for method parameter type Integer is present but converted to null

콘솔에 위와 같이 잘 찍힌다

 


이번엔 Post요청에 아무 값도 넣지 말고 보내보자

<java />
ConstraintViolationImpl{interpolatedMessage='1 이상이어야 합니다', propertyPath=get.age, rootBeanClass=class com.example.exception.controller.ApiController, messageTemplate='{javax.validation.constraints.Min.message}'} ConstraintViolationImpl{interpolatedMessage='크기가 2에서 2147483647 사이여야 합니다', propertyPath=get.name, rootBeanClass=class com.example.exception.controller.ApiController, messageTemplate='{javax.validation.constraints.Size.message}'}

콘솔에는 위와 같이 찍힌다

 

 

많은 값들이 있으며 가져다 쓰면 된다

 

값들을 가져다쓰기 위해 코드를 바꿔보자

<java />
@ExceptionHandler(value= ConstraintViolationException.class) public ResponseEntity constraintViolationException(ConstraintViolationException e){ //어떠한 필드가 잘못되었을때의 정보를 담고 있음 e.getConstraintViolations().forEach(error ->{ String field=error.getLeafBean().toString(); String message=error.getMessage(); String value=error.getInvalidValue().toString(); System.out.println("---------------"); System.out.println(field); System.out.println(message); System.out.println(value); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); }

필드명이 제대로 나오지 않아서 코드를 바꿔보자

 

<code />
package com.example.exception.advice; import com.example.exception.controller.ApiController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolationException; import javax.validation.Path; import java.lang.reflect.Field; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @RestControllerAdvice(basePackageClasses = ApiController.class) //ApiController에서만 작동하게 된다 public class ApiControllerAdvice { ////ApiController에서만 작동하게 되니까 Global에서 Api로 바꿔줌 @ExceptionHandler(value = Exception.class) public ResponseEntity exception(Exception e){ System.out.println(e.getClass().getName()); //어떤 클래스에서 에러가 났는지 확인! return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); } @ExceptionHandler(value= MethodArgumentNotValidException.class) //인자가 없을 때 발생하는 에러 잡기 public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){ BindingResult bindingResult = e.getBindingResult(); bindingResult.getAllErrors().forEach(error->{ FieldError field = (FieldError) error; //형변환 String fieldName=field.getField(); String message = field.getDefaultMessage(); String value = field.getRejectedValue().toString(); System.out.println("---------------"); System.out.println(fieldName); System.out.println(message); System.out.println(value); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } @ExceptionHandler(value= ConstraintViolationException.class) public ResponseEntity constraintViolationException(ConstraintViolationException e){ //어떠한 필드가 잘못되었을때의 정보를 담고 있음 e.getConstraintViolations().forEach(error ->{ Stream<Path.Node> stream = StreamSupport.stream(error.getPropertyPath().spliterator(),false); List<Path.Node> list = stream.collect(Collectors.toList()); String field=list.get(list.size()-1).getName(); String message=error.getMessage(); String value=error.getInvalidValue().toString(); System.out.println("---------------"); System.out.println(field); System.out.println(message); System.out.println(value); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } @ExceptionHandler(value= MissingServletRequestParameterException.class) public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e){ String fieldName=e.getParameterName(); String fieldType=e.getParameterType(); String invalidValue=e.getMessage(); System.out.println(fieldName); System.out.println(fieldType); System.out.println(invalidValue); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } }

이제 에러메시지를 예쁘게 꾸며보자!

 

dto 패키지에

Error.java

<code />
package com.example.exception.dto; public class Error { private String field; private String message; private String invalidValue; public String getField() { return field; } public void setField(String field) { this.field = field; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getInvalidValue() { return invalidValue; } public void setInvalidValue(String invalidValue) { this.invalidValue = invalidValue; } }

 

ErrorResponse.java

<code />
package com.example.exception.dto; public class Error { private String field; private String message; private String invalidValue; public String getField() { return field; } public void setField(String field) { this.field = field; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getInvalidValue() { return invalidValue; } public void setInvalidValue(String invalidValue) { this.invalidValue = invalidValue; } }

 

그리고 ApiControllerAdvice.java 코드 수정

<java />
@ExceptionHandler(value= MethodArgumentNotValidException.class) //인자가 없을 때 발생하는 에러 잡기 public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest httpServletRequest){ //5. httpServletRequest받아오기 List<Error> errorList = new ArrayList<>(); //1. 배열 만들어주고 BindingResult bindingResult = e.getBindingResult(); bindingResult.getAllErrors().forEach(error->{ FieldError field = (FieldError) error; //형변환 String fieldName=field.getField(); String message = field.getDefaultMessage(); String value = field.getRejectedValue().toString(); System.out.println("---------------"); System.out.println(fieldName); System.out.println(message); System.out.println(value); Error errorMessage = new Error(); //2. errorMessage 객체 만들어 준 뒤 errorMessage.setField(fieldName); //3. fieldName 넣어준다 errorMessage.setMessage(message); errorMessage.setInvalidValue(value); errorList.add(errorMessage); }); ErrorResponse errorResponse = new ErrorResponse(); //4. errorResponse 객체 만들기 errorResponse.setErrorList(errorList); errorResponse.setMessage(""); errorResponse.setRequestUrl(httpServletRequest.getRequestURI()); //어디를 요청했는데 에러가 났는지 errorResponse.setStatusCode(HttpStatus.BAD_REQUEST.toString()); errorResponse.setResultCode("FAIL"); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); //6. body에 errorResponse 심어주기 }

 

위, 아래에 있는 예외 처리 메서드들에도

위 코드에서 반복되는 코드들을 넣어준다

 

그러면 Get 요청에 인자 없이 보내면

Body에 예쁘게 결과가 나온다!

 

Post 요청도 잘못 보내면

 

역시 예쁘다

 

 

엔터프라임 프레임워크같이

컨트롤러가 4,5개가 되는 것들/주소가 10개 이상 나온다면

Validatrion 적용하고

ApiControllerAdvice같은 예외처리 자바 코드를 짜야한다.

'Spring' 카테고리의 다른 글

Spring boot - Filter  (0) 2022.06.18
Spring - DTO를 사용하는 이유  (0) 2022.06.16
Exception 처리  (0) 2022.06.06
Custom Validation  (0) 2022.06.05
Validation  (0) 2022.06.01
profile

Burninghering's Blog

@개발자 김혜린

안녕하세요! 반갑습니다.