기억보다 기록을

[Springboot] aop advice 사용한 exception 처리 (@ControllerAdvice ,@ExceptionHandler, Validation 사용) 본문

Development/Springboot

[Springboot] aop advice 사용한 exception 처리 (@ControllerAdvice ,@ExceptionHandler, Validation 사용)

juyeong 2023. 2. 22. 18:06
반응형

MVC 패턴에서 개발자에게 컨트롤러는 데이터를 뿌리는 최종 관문과 같다는 생각이 듭니다. 이 때 컨트롤러는 @Controller와 @RestController 두가지 어노테이션을 사용하여 IoC 할 수 있는데, 리턴타입에 따라 다르게 사용합니다.

파일을 리턴하고 싶다면 컨트롤러, 데이터를 리턴하고 싶다면 레스트 컨트롤러를 사용해요.

좀 더 직관적으로 비유하자면 지금은 스프링부트에서 버린 (...) JSP 파일을 리턴하고 싶다면 컨트롤러 어노테이션을 쓰면 됩니다.

Jsp 파일을 리턴하니까요. 하지만 Json 타입의 데이터를 리턴하려면 @RestController를 사용해야합니다.

요즘은 SPA방식을 선호하여 대부분 레스트풀하게 데이터를 뿌리는 것 같아요.

이 때, 파일타입도 리턴하고 싶고 데이터 타입도 동시에 리턴하고 싶다면 어떻게 해야할까요? 

글로벌한 예외처리는 두 개의 다른 리턴 타입을 가능하게 해줘요.

(@Controller에서 데이터를 리턴하고 싶다면 메소드의 리턴타입에 @Resposebody 를 붙이는 방법도 있습니다.)

여기서 글로벌한 예외처리는 @ControllerAdvice + @ExceptionHandler의 조합입니다. 

사이드 프로젝트의 회원가입 기능을 작성하던 중 사용자 유효성 검사를 진행했습니다. 이 때 어떻게 예외처리를 했는지 정리한 글입니다.

물론 프론트에서 막는 것이 간단하지 않나? 라고 생각할 수 있지만

만약 누군가 API로 데이터를 직접 쏜다면? 당연히 서버에서도 대책이 있어야합니다.

 

 

 

[문제상황]

회원가입시 20자 이상 작성은 금지되어 있는데, 20자를 초과한 클라이언트가 리퀘스트를 날렸습니다. 

그 결과 다음과 같은 에러페이지가 발생합니다. 

이런 익셉션 페이지는 사용자 UX 측면에서 좋지 않기에 제이슨 타입으로 에러 메세지를 내리는 것이 가장 깔끔할 것입니다. 

익셉션이 발생했을 때 이런 페이지가 나오지 않도록

 

 

[해결방법]

[1] 핸들링 해줄 패키지(handler)와 클래스를 작성합니다.

 

1. CustomValidationException

public class CustomValidationException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    private String message;
    private Map<String,String> errMap;

    public CustomValidationException(String message, Map<String,String> errMap) {
        super(message); 
        this.message = message;
        this.errMap = errMap;
    }

    public Map<String, String> getErrMap () {
        return errMap;
    }
}

에러메시지를 내릴 에러맵을 하나 생성합니다.

 

2. ControllerExceptionHandler

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public Map<String, String> validationException(CustomValidationException e) {
        return e.getErrMap();
    }

}

 

 

[2] 컨트롤러에서 익셉션을 호출합니다. 

   @PostMapping("/auth/signup")
    public String signup (@Valid SignupReqDto signupReqDto, BindingResult bindingResult) {
        if(bindingResult.hasErrors()) {
            Map<String, String> errMap = new HashMap<>();
            for (FieldError err : bindingResult.getFieldErrors()) {
                errMap.put(err.getField(),err.getDefaultMessage());
            }
            throw new CustomValidationException("로그인 유효성검사 실패함", errMap);

        } else {
            User user = signupReqDto.toEntity();
            log.info(user.toString());
            authService.signUp(user);
            return "auth/signin";

        }

Spring Validation의 BindingResult가 hasErrors() 메소드로 에러를 발견하면

선언한 errMap에 메시지를 넣어줍니다. 그 다음 CustomValidationException을 던집니다. 

 

 

[3] 익셉션을 던지면 CustomValidationException 에서 ControllerExceptionHandler의 errMap을 리턴합니다. 

이렇게 Json타입의 데이터가 잘 내려오는 것을 확인할 수 있습니다 😊

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형