✏️ 필요성
- Rest Controller 를 구현할 때 비즈니스 로직 수행 중 문제가 발생할 경우 이를 핸들링 하기 위한 기능이다.
- 서버의 문제인지 클라이언트의 문제인지 원인을 클라이언트에게 응답할 수 있다.
- 만약 예외처리를 하지 않는다면 모든 exception 이 500 에러로 발생하고,
정확한 원인의 메시지도 받을 수 없다.
- 500 에러는 서버의 문제이기 때문에 클라이언트 입장에선 혼란스러울 수 밖에 없다.
✏️ ExceptionHandler 구현
📍 Exception 발생시키기
- Servcice 계층에 username 으로 Member 객체를 찾아서 반환해주는 간단한 비즈니스 로직이다.
- 만약 Optional 에 값이 없다면 Exception 이 발생하도록 했다.
- NotFoundException 은 직접 커스텀한 예외이다.
public Member findByUsername(String username) {
Optional<Member> byUsername = memberRepository.findByUsername(username);
if (byUsername.isPresent())
return byUsername.get();
throw new NotFoundException("존재하지 않는 username");
}
- 이대로 예외를 발생시키면 잘못된 요청임에도 불구하고 500 에러가 발생한다.
- 클라이언트 입장에선 500 이외의 정보를 얻을 수 없기 때문에 서버에 문제가 있다고 생각할 수 밖에 없다.
📍 Response 객체 생성
- Exception 의 Msg 를 맵핑해 클라이언트에게 전달하는 객체이다.
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@AllArgsConstructor
public class ErrorResponse {
protected String errorMsg;
}
📍 Exception 객체
- 발생되는 Exception 의 제목을 정해주는 작업이라고 생각하면 된다.
- 예제에서는 값을 찾지 못했을 때 사용할 예외를 생성했다.
import com.atowz.global.exception.ui.ErrorStatus;
import io.jsonwebtoken.JwtException;
import lombok.Getter;
@Getter
public class InvalidJwtException extends JwtException {
private final int status;
public InvalidJwtException(ErrorStatus response) {
super(response.getErrorMessage());
this.status = response.getStatus();
}
}
📍 Exception status 객체
- 커스텀 예외들의 상태를 직접 정의해 통일성과 유지보수성을 높힐 수 있다.
import lombok.Getter;
@Getter
public enum ErrorStatus {
JWT_INVALID("유효하지 않은 토큰", 401),
JWT_EXPIRED("만료된 토큰", 401),
JWT_NOT_FOUND("토큰을 찾을 수 없음", 400),
JSON_DOSE_NOT_SUPPORT("json String 으로 변환 실패", 400);
private final String errorMessage;
private final int status;
ErrorStatus(String errorMessage, int status) {
this.errorMessage = errorMessage;
this.status = status;
}
}
📍 RestControllerAdvice 계층
ExceptionHandler
를 적용하기 위한 객체이다.
@RestControllerAdvice
를 선언하지 않는다면 Exception 이 발생해도 method 가 실행되지 않는다.
@ExceptionHandler
- Excption 을 매핑하는 어노테이션이다.
- 속성값에 Exception 을 명시하지 않으면 모든 Exception 에 대한 핸들링을 하기 때문에 속성값을 꼭 적어주어야 한다.
import com.atowz.global.exception.jwt.InvalidJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class ExceptionController {
@ExceptionHandler(InvalidJwtException.class)
public ResponseEntity<ErrorResponse> invalidJwtExceptionHandler(InvalidJwtException e) {
log.error(e.getMessage());
return ResponseEntity.status(e.getStatus()).body(new ErrorResponse(e.getMessage()));
}
}
📍 사용 방법
- exception 을 발생시키고 인자에 enum 으로 생성한 ErrorStatus 를 값으로 주면 통일성을 향상시킬 수 있다.
public void isAccessTokenValid(String accessToken) {
String value = redisUtil.getValue(accessToken);
if (value != null) {
throw new InvalidJwtException(JWT_EXPIRED);
}
}
- 이제 Exception 이 발생하면 오류코드와 함께 message 를 같이 응답 할 수 있다.