BackEnd/패캠

Exception 처리

제이드Jade 2022. 1. 22. 00:00

- validation 을 위반해서 생긴 exception을 처리해보자

- exception을 잡아서 처리할 수도 있고, 통틀어서 Exception(최상위)로만 잡아줄 수 있다.

- 처리하는 방법 2가지

           1) @RestControllerAdvice => 클래스를 만들어서 Global Package에 사용, 하지만 basePackageClasses를 적용하면 특정한 클래스에만 적용시킬 수 있다. / 이 안에 2가 있다.

           2) @ExceptionHandler => 각 오류를 잡을 메서드에 붙이는 것

 

- ApiController

@Validated //컨트롤러에도 validate해서 요청 파라미터를 검증할 수 있게 함
@RestController
@RequestMapping("/api")
public class ApiController {    //에러처리를 해보자!

    @GetMapping("")
    public User get(
                    @Size(min=2)
                    @RequestParam String name,

                    @Min(1)
                    @RequestParam Integer age){   //@RequestParam(required=false)-> 안들어와도 되고, 안들어오면 null로 셋팅됨
        User user=new User();

        user.setName(name);
        user.setAge(age);
        //파라미터로 안주면 age는 null이 되고, null값을 set하게 요청했으므로 exception 발생 => Internal Server Error가 일어남
        //즉, exception 처리를 해줘야 함.
        /*방법1) @ControllerAdvice => 1)Global package에 다 적용/ 2)특정 Controller로 지정가능
          방법2) @ExceptionHandler => 특정 Controller에서만 됨
          * 방법1 클래스를 구현하면 그 안에 방법2에도 쓰이는 @ExceptionHandler 메서드들이 있다.
            방법2는 그 메서드들이 컨트롤러안에 있다. */

        int a=age+10;

        return user;
    }

    @PostMapping("")
    public User post(@Valid @RequestBody User user){     //@Valid가 오류가 날때가 있음 => 걍 invalidate caches / restart
        System.out.println(user);
        return user;
    }

    //방법2 //특정 예외 처리(global보다 우선시)
//    @ExceptionHandler(value = MethodArgumentNotValidException.class)
//    public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e){
//        System.out.println("api controller");
//        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
//    }

}

 

- ApiControllerAdvice (방법1 클래스)

@RestControllerAdvice(basePackageClasses= ApiController.class) //basePackageClasses= 하면 방법1의 global이 아닌 방법2와 같은 기능
public class ApiControllerAdvice {

    @ExceptionHandler(value=Exception.class) //value=처리하고 싶은 예외, 여기선 모든 예외
    public ResponseEntity exception(Exception e){   //위의 value를 매개변수로 받는다.(클래스 이름 동일해야함)
        System.out.println("=====================");
        System.out.println(e.getClass().getName());
        System.out.println(e.getLocalizedMessage());
        System.out.println("=====================");

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("");
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)  //post 요청객체 에러, 특정 예외 처리(위 메서드보다 우선시)
    public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest httpServletRequest){
        BindingResult bindingResult=e.getBindingResult();   //bindingResult... 에러결과를 담아놓은 곳(?)

        List<ErrorMsg> errors=new ArrayList<>();

        bindingResult.getAllErrors().forEach(error -> {
            FieldError field=(FieldError) error;

            String fieldName=field.getField();
            String message=field.getDefaultMessage();
            String value=field.getRejectedValue().toString();   //입력된 (잘못된) 값

            ErrorMsg errorMessage=new ErrorMsg();
            errorMessage.setFieldName(fieldName);
            errorMessage.setMessage(message);
            errorMessage.setInvalidValue(value);

            errors.add(errorMessage);
        });

        ErrorResponse errorResponse=new ErrorResponse();
        errorResponse.setErrorList(errors);
        errorResponse.setMessage("");
        errorResponse.setRequestUrl(httpServletRequest.getRequestURI());
        errorResponse.setResultCode(HttpStatus.BAD_REQUEST.toString());
        errorResponse.setCode("FAIL");


        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(value= MissingServletRequestParameterException.class) //get에서 받은 param으로 객체를 만들 때 객체 제약조건 위반
    public ResponseEntity missingServletRequestParameterException(MissingServletRequestParameterException e,HttpServletRequest httpServletRequest){
        List<ErrorMsg> errors=new ArrayList<>();

        String fieldName=e.getParameterName();      //필드명, 위에 예외는 이거 지원 안해줘서 bindingResult 썼었는데...\
        String message=e.getMessage();

        ErrorMsg errorMessage=new ErrorMsg();
        errorMessage.setFieldName(fieldName);
        errorMessage.setMessage(message);

        errors.add(errorMessage);

        ErrorResponse errorResponse=new ErrorResponse();
        errorResponse.setErrorList(errors);
        errorResponse.setMessage("");
        errorResponse.setRequestUrl(httpServletRequest.getRequestURI());
        errorResponse.setResultCode(HttpStatus.BAD_REQUEST.toString());
        errorResponse.setCode("FAIL");

        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(value= ConstraintViolationException.class)  // get에서 requestParam 제약조건을 위반
    public ResponseEntity constraintViolationException(ConstraintViolationException e, HttpServletRequest httpServletRequest){

        List<ErrorMsg> errors=new ArrayList<>();

        e.getConstraintViolations().forEach(error->{    //제약 위반 요소들
            //객체의 필드들을 뽑아오는게 좀 번거롭..
            Stream<Path.Node> stream= StreamSupport.stream(error.getPropertyPath().spliterator(),false);
            List<Path.Node> list=stream.collect(Collectors.toList());

            String fieldName=list.get(list.size()-1).getName();
            String message=error.getMessage();
            String value=error.getInvalidValue().toString();

            ErrorMsg errorMessage=new ErrorMsg();
            errorMessage.setFieldName(fieldName);
            errorMessage.setMessage(message);
            errorMessage.setInvalidValue(value);

            errors.add(errorMessage);
        });

        ErrorResponse errorResponse=new ErrorResponse();
        errorResponse.setErrorList(errors);
        errorResponse.setMessage("");
        errorResponse.setRequestUrl(httpServletRequest.getRequestURI());
        errorResponse.setResultCode(HttpStatus.BAD_REQUEST.toString());
        errorResponse.setCode("FAIL");
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }
}

 

* 클라이언트에게 잘 짜여진 응답을 보내기 위해 만든 오류 응답 객체 (advice에서 이걸 body)

- ErrorResponse

public class ErrorResponse {
    private String statusCode;
    private String requestUrl;
    private String code;
    private String message;
    private String resultCode;

    private List<ErrorMsg> errorList;

 

- Error

public class ErrorMsg {
    private String fieldName;
    private String message;
    private String invalidValue;