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

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

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

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 코드 바꿔주자

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 요청 보냈더니 아래와 같은 에러가 뜬다

org.springframework.web.bind.MissingServletRequestParameterException

 

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

 


또 다른 예제

제한을 바꿔보자!

@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;
}

javax.validation.ConstraintViolationException

 

ApiControllerAdvice.java에 

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

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

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

생기는 에러 

org.springframework.web.bind.MissingServletRequestParameterException

ApiControllerAdvice.java에

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

 

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

Post 요청에 값 안넣었을 때

오류 잡기

@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에 에러문이 뜬다

 

여태까지의 총 코드

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요청에 값에 아무것도 넣지 않고 보냈을 때

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

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

 

맨 아래

missingServletRequestParameterException

함수를 거친 것이다!

 

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

@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());
}

 

결과

age
Integer
Required request parameter 'age' for method parameter type Integer is present but converted to null

콘솔에 위와 같이 잘 찍힌다

 


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

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}'}

콘솔에는 위와 같이 찍힌다

 

 

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

 

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

    @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());
    }

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

 

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

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

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 코드 수정

    @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

@개발자 김혜린

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