본문 바로가기
Spring/Spring

스프링 Thymeleaf에서 @ExceptionHandler 활용

by 델버 2023. 1. 17.
  • 로그인을 처리하는데 예외처리에서 문제가 생겼다. 로그인을 하게되면 service에서 repository를 불러와 아이디를 가지고 있는 사용자인지, 비밀번호가 맞는지를 체크하고 만약 실패하면 IllegalArgumentException을 던진다.
@Transactional
public Member login(LoginRequestDto dto) {

	Member member = memberRepository.findByLoginEmail(dto.getEmail())
	        .orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다."));
	
	if (!dto.getPassword().equals(member.getPassword())) {
	    throw new IllegalArgumentException("비밀번호가 맞지 않습니다.");
	}

	return member;
}
  • 문제는 이 예외를 controller에서 어떻게 처리하여 뷰를 넘겨 thymeleaf가 받을 수 있게 주는 것인가이다. 에러 페이지가 아닌 다시 로그인 화면을 넘겨주어 그곳에 메시지를 보여주려고 한다.

1. try catch

@PostMapping("/login")
public String login(@Validated @ModelAttribute LoginRequestDto dto,
                    BindingResult bindingResult,
                    @RequestParam(defaultValue = "/") String redirectURL,
                    HttpServletRequest request) {

    if (bindingResult.hasErrors()) {
        return "login/loginForm";
    }

    Member member;
    try {
        member = loginService.login(dto);
    } catch (IllegalArgumentException e) {
        bindingResult.reject("globalError", e.getMessage());
    }

    HttpSession session = request.getSession();
    session.setAttribute("login_member", member);

    return "redirect:" + redirectURL;
}
  • try catch로 처리하여 넘겨주는 방법은 해당 Exception을 잡아 넘겨주는 것이다. 그런데 만약 여러 Exception이 만들어지게 될 겨우 모두 잡아줘야 한다는 것과 가독성, 번거로움이 있다.

2. @ExceptionHandler

@ExceptionHandler(IllegalArgumentException.class)
public String ex(IllegalArgumentException e, Model model) {
    model.addAttribute(new LoginRequestDto());
    return "login/loginForm";
}
  • ExceptionHandler를 사용하는 것도 좋지만 내 상황에선 뷰를 넘겨야 하는데 로그인 폼에 맞는 dto를 넘겨줘야 한다는 번거로운 문제가 있다.

해결

  • 문제 자체는 반환 값을 뷰를 넘겨주려고 해서 그런 것이다. 이걸 api 방식으로 바꾸어 넘겨주려고 한다. 해결하려는 순서는 이렇다.
  1. 명확한 오류 코드와 메시지 관리를 위해 커스텀 Exception을 만든다.
  2. exception들을 ExceptionHandler로 처리하여 api로 넘겨준다.
  3. 기존 form에서 submit하던 방식을 JS로 요청하여 api로 응답받는다.

1. 커스텀 Exception

  • 기존 사용하던 IllegalArgumentException을 더 명확히 API 응답을 하기위해 커스텀 Exception을 만들어 응답하려고 한다.
// LoginException.class
@Getter
public class LoginException extends RuntimeException {

    private final LoginExceptionCode loginExceptionCode;

    public LoginException(LoginExceptionCode loginExceptionCode) {
        super(loginExceptionCode.getMessage());
        this.loginExceptionCode = loginExceptionCode;
    }
}
// LoginExceptionCode.enum
@Getter
public enum LoginExceptionCode {

    LOGIN_NOTFOUND_MEMBER(HttpStatus.BAD_REQUEST, "LOGIN_001", "존재하지 않는 회원입니다."),
    LOGIN_PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST, "LOGIN_002", "비밀번호가 맞지 않습니다.");

    private final HttpStatus status;
    private final String code;
    private final String message;

    LoginExceptionCode(HttpStatus status, String code, String message) {
        this.status = status;
        this.code = code;
        this.message = message;
    }
}
  • LoginException이라는 커스텀 exception을 만들고 enum으로 오류 코드를 만들어 줬다.
// LoginService.class
@RequiredArgsConstructor
@Service
public class LoginService {

    private final MemberRepository memberRepository;

    @Transactional
    public Member login(LoginRequestDto dto) {

        Member member = memberRepository.findByLoginEmail(dto.getEmail())
                .orElseThrow(() -> new LoginException(LoginExceptionCode.LOGIN_NOTFOUND_MEMBER));

        if (!dto.getPassword().equals(member.getPassword())) {
            throw new LoginException(LoginExceptionCode.LOGIN_PASSWORD_MISMATCH);
        }

        return member;
    }
}
  • 기존 사용하던 IllegalArgumentException에서 LoginException를 발생하여 오류 코드로 더 명확해졌다.
@Getter
public class ExceptionResponse {

    private final HttpStatus status;
    private final String code;
    private final String message;

    public ExceptionResponse(ExceptionCode exceptionCode) {
        this.status = exceptionCode.getStatus();
        this.code = exceptionCode.getCode();
        this.message = exceptionCode.getMessage();
    }
}

 

@ExceptionHandler(LoginException.class)
public ResponseEntity<ExceptionResponse> loginExHandle(LoginException e) {
    log.error("[loginExHandle] ex", e);
    ExceptionResponse exceptionResponse = new ExceptionResponse(e.getLoginExceptionCode());

    return ResponseEntity.status(exceptionResponse.getStatus()).body(exceptionResponse);
}
  • ResponseEntity를 반환하는 곳에 정보를 넘겨주었다.
  • 이제 이 정보를 가지고 뷰에서 보여주면 끝


참고

'Spring > Spring' 카테고리의 다른 글

[JWT] JWT에 대해서  (0) 2023.01.21
스프링 BindingResult 에러 메시지 JSON으로 응답하기  (0) 2023.01.17
Interface로 추상화하여 Enum 사용  (0) 2023.01.11
[Spring] spring 버전  (0) 2022.12.20

댓글