Spring Tips (2)

김재익·2023년 6월 29일
0

SPRING FRAMEWORK

목록 보기
5/6
post-thumbnail

valdation

ExceptionHandler

@Valid 애너테이션을 통해 우리는 Controller로 오는 정보들의 유효성을 검사 할 수 있다. 이 때 유효성 검사에서 통과하지 못하면 MethodArgumentNotValidException이 발생한다.

이 예외가 발생했을 때 대처하는 방법은 두가지가 있다.

BindingResult

하나는 Controller내부 mapping 메서드들 마다 매개변수로 BindingResult를 받아 hasErrors() 메서드를 사용해서 예외가 발생했는지 확인하고 후처리를 하는 방법.

@Controller
@RequestMapping(value = "/member")
public class TestController {
    
    @PostMapping
    @ResponseBody
    public ResponseEntity saveMember(@Valid Member member, BindingResult bindingResult) {
        
        if (bindingResult.hasErrors()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(bindingResult.getAllErrors());
        }
        
        // member save code
        
        return ResponseEntity.ok(member);
    }
}

@ExceptionHandler

또 하나는 @ExceptionHandler 애너테이션을 사용해서 MethodArgumentNotValidException을 감지하도록 만들고 그에 따른 responseEntity를 만들어 처리하는 방법.

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ExceptionDTO> validationExceptionHandler(MethodArgumentNotValidException e){
	Map<String, String> errors = new LinkedHashMap<>();
    e.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
    ExceptionDTO errorResponse = new ExceptionDTO("false",400, errors);
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

유효성 검사가 여러개 시행 될 경우 여러개를 담아야하기 때문에 위 코드에서는 Map에 에러요약을 담고 body에 담아 반환했다.

생각

우리는 Controller에서 여러종류의 Exception을 만난다. NULL은 우리의 주적이며 특정한 상태일 때 예외를 처리해야할 경우 Exception을 직접 만들어 던지기도 하는데 이 때 @ExceptionHandler를 사용한다면 다양한 예외발생을 핸들링 할 수 있을 것이다.

그럼 이 ExceptionHandler를 모아 둘 수는 없을까?

왜 없겠나

ControllerAdvice

@ControllerAdvice를 클래스레벨에 선언하면 그 클래스가 존재하는 디렉토리의 하위 디렉토리까지 Controller가 있다면 거기서 발생하는 모든 Exception을 그 클래스가 핸들링 할 수 있다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<BaseResponseDTO> runtimeExceptionHandler(Exception e){
        BaseResponseDTO errorResponse = new BaseResponseDTO("false",400);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    // requestDTO 유효성 검사 실패
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ExceptionDTO> validationExceptionHandler(MethodArgumentNotValidException e){
        Map<String, String> errors = new LinkedHashMap<>();
        e.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
        ExceptionDTO errorResponse = new ExceptionDTO("false",400, errors);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    // 비밀번호 불일치
    @ExceptionHandler(PasswordMismatchException.class)
    public ResponseEntity<ExceptionDTO> passwordMismatchExceptionHandler(PasswordMismatchException e){
        Map<String, String> errors = Collections.singletonMap("error", e.getMessage());
        ExceptionDTO errorResponse = new ExceptionDTO("false",401, errors);
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
    }

    // 유저 정보 없음
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ExceptionDTO> userNotFoundExceptionHandler(UserNotFoundException e){
        Map<String, String> errors = Collections.singletonMap("error", e.getMessage());
        ExceptionDTO errorResponse = new ExceptionDTO("false",404, errors);
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }
}

자매품 @RestControllerAdvice도 있다. 이름에서 알 수 있듯 @RestControllerAdvice는 @ResponseBody 이 있기 때문에 반환하는 객체를 body에 담아 JSON 형식으로 반환해준다.

profile
개발자호소인

0개의 댓글