๐ ์คํ๋ง ์๋ฌ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ
์คํ๋ง์ ํ๋ก๊ทธ๋จ ์๋ ์ค ์ฌ๋ฌ ์๋ฌ๋ฅผ ๋ฐ์์ํจ๋ค.
์ด์ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค.
๐น try-catch
try {
Jwts.parserBuilder().setSigningKey(getSecretKey()).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT Token", e);
}
๋จ์ํ try-catch๋ฅผ ์ฌ์ฉํ ์์ธ์ฒ๋ฆฌ์ด๋ค.
์ด๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ผ๋, ์ฝ๋์ ์ค๋ณต์ด ๋ง์ด ๋ฐ์ํ ๊ฒ์ด๋ค.
๐น @ControllerAdvice
์ ์ด๋ ธํ ์ด์ ์ ํตํด @ExceptionHandler๋ฅผ ์ปจํธ๋กค๋ฌ ์ ์ฒด์ ์ ์ฉ์ํฌ ์ ์๋ค.
์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ค๋ฉด ๋ ํธํ๊ณ ๊น๋ํ๊ฒ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค.
์๋๋ @ControllerAdvice, @ExceptionHandler์ ๋ํ ์ค๋ช ์ด๋ค.
๐บ@ControllerAdvice
์ ์ญ์ ์ผ๋ก ์ ์ฉํ ์ ์๋ ์์ธ์ฒ๋ฆฌ์ ๋ฐ์ธ๋ฉ ์ค์ ์ ์ง์ํ๋ ์ด๋ ธํ ์ด์ ์ด๋ค.๐บ @ExceptionHandler
์ปจํธ๋กค๋ฌ์์ ๋ฐ์ํ๋ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์ด๋ ธํ ์ด์ ์ด๋ค.๐ ์คํ๋ง ์๋ฌ ์ฒ๋ฆฌ ๊ตฌํ
๐นGlobal Exception Handler
์ด๋ฅผ ๋ง๋ค๊ธฐ ์ํด์ ์๋ฌ ๊ฐ์ฒด๋ค์ hierarchy๋ฅผ ๋จผ์ ๊ณ ๋ คํด์ผ ํ๋ค.
์ธ์ธํ ์๋ฌ๋ค, ์ฆ null pointer exception ๊ฐ์ ์๋ฌ๋ ๋ชจ๋ RuntimeException๋ฅผ ์์๋ฐ๊ณ ,
์ด RuntimeException์ Exception์ ์์๋ฐ๋๋ค.
์ด๋ Exception.class ์๋ฌ์ ๋ํ ์๋ต์ ๊ตฌํํ๋ฉด, ์ด ์๋ฌ ๊ฐ์ฒด๋ฅผ ์์๋ฐ๋ ๋ชจ๋ ์๋ฌ ๊ฐ์ฒด๋ฅผ ์ฒ๋ฆฌ ๊ฐ๋ฅํ๋ค๋ ๋ง์ด๋ค.
์ฆ, global exception handler๋ฅผ ํตํด ์ฒ๋ฆฌํ์ง ์์ ์๋ฌ๋ค์ ๋ชจ๋ Exception์ผ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅํ๋ค.
@ControllerAdvice
@ApiIgnore
@Slf4j
public class GlobalExceptionHandler {
// @Valid ์ผ๋ก binding error ์ ๋ฐ์
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException MANVE) {
log.error("MethodArgumentNotValidException", MANVE);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._BAD_REQUEST),
null, ErrorCode._BAD_REQUEST.getHttpStatus());
}
// ์ง์ํ์ง ์๋ http method ํธ์ถ์ ๋ฐ์ํ๋ ์๋ฌ
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("HttpRequestMethodNotSupportedException", e);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._METHOD_NOT_ALLOWED),
null, ErrorCode._METHOD_NOT_ALLOWED.getHttpStatus());
}
// JSON parse error ์ผ ๊ฒฝ์ฐ์ ๋ฐ์ํฉ๋๋ค
// request ๊ฐ์ ์ฝ์ ์ ์์ ๋ ๋ฐ์ํฉ๋๋ค.
@ExceptionHandler(HttpMessageNotReadableException.class)
protected ResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("HttpMessageNotReadableException", e);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._BAD_REQUEST),
null, ErrorCode._BAD_REQUEST.getHttpStatus());
}
// ๋ณ์ ํ์
์ด ๋ง์ง ์์ ๋ ๋ฐ์ํ๋ ์๋ฌ์
๋๋ค.
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error("MethodArgumentTypeMismatchException", e);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._BAD_REQUEST),
null, ErrorCode._BAD_REQUEST.getHttpStatus());
}
// ๋ณ์ ํ์
์ด ๋ง์ง ์์ ๋ ๋ฐ์ํ๋ ์๋ฌ์
๋๋ค.
@ExceptionHandler(EmptyResultDataAccessException.class)
protected ResponseEntity<ErrorResponse> handleEmptyResultDataAccessException(EmptyResultDataAccessException e) {
log.error("EmptyResultDataAccessException", e);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._BAD_REQUEST),
null, ErrorCode._BAD_REQUEST.getHttpStatus());
}
// The call was transmitted successfully, but Amazon S3 couldn't process it.
@ExceptionHandler(AmazonServiceException.class)
protected ResponseEntity<ErrorResponse> handleAmazonServiceException(AmazonServiceException e) {
log.error("AmazonServiceException", e);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._BAD_REQUEST),
null, ErrorCode._BAD_REQUEST.getHttpStatus());
}
// thrown when service could not be contacted for a response, or when client is unable to parse the response from service.
@ExceptionHandler(SdkClientException.class)
protected ResponseEntity<ErrorResponse> handleSdkClientException(SdkClientException e) {
log.error("SdkClientException", e);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._BAD_REQUEST),
null, ErrorCode._BAD_REQUEST.getHttpStatus());
}
// ๋น์ฆ๋์ค ๋ก์ง ์๋ฌ ์ฒ๋ฆฌ
@ExceptionHandler(BaseException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(final BaseException baseException) {
log.error("handleBusinessException", baseException);
return new ResponseEntity<>(ErrorResponse.onFailure(baseException.getErrorCode()),
null, baseException.getErrorCode().getHttpStatus());
}
// ์์์ ๋ฐ๋ก ์ฒ๋ฆฌํ์ง ์์ ์๋ฌ๋ฅผ ๋ชจ๋ ์ฒ๋ฆฌํด์ค๋๋ค.
@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(Exception exception) {
log.error("handleException", exception);
return new ResponseEntity<>(ErrorResponse.onFailure(ErrorCode._INTERNAL_SERVER_ERROR),
null, INTERNAL_SERVER_ERROR);
}
}
์ ์ฝ๋๋ Global Exception์ ์ฒ๋ฆฌํ๋ ์ฝ๋์ด๋ค.
@ExceptionHandler๋ฅผ ์ด์ฉํด ํด๋น ์๋ฌ๊ฐ ๋ฐ์ํ ๋๋ง๋ค ์๋ฌ ๋ฆฌ์คํฐ์ค๋ฅผ ์ ์ํ๋ค.
์๋น์ค ๋ก์ง ์งํ ์ค, ๋ฐ์์ํค๋ ์๋ฌ๋ BaseException์ด๋ผ๋ ์ปค์คํ ์๋ฌ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ฒ๋ฆฌํด์คฌ๋ค. ์ด๋ ๋์ค์ ์ค๋ช ํ๊ฒ ๋ค.
์์์ ์ค๋ช ํ ๋ฐ์ ๊ฐ์ด, ์ ์ฝ๋์ ๋ง์ง๋ง ๋ถ๋ถ์ Exception.class์ ๋ํ ๋ฆฌ์คํฐ์ค๋ฅผ ์ ์ํ ๋ถ๋ถ์ด ์๋ค. ๋ด๊ฐ ์ฒ๋ฆฌํ์ง ์์ ๋ถ๋ถ์ ๋ชจ๋ 500๋ฒ ์๋ฒ ์๋ฌ๋ฅผ ๋์ฐ๋๋ก ๊ตฌํํ๋ค.
์๋ฌ ๋ฆฌ์คํฐ์ค ํ์์ ์๋๋ฅผ ๋ณด์.
@Getter
public class ErrorResponse {
private final String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
private final boolean success = false;
private String code;
private int status;
private String message;
@Builder
public ErrorResponse(int status, String message, String code) {
this.status = status;
this.message = message;
this.code = code;
}
public static ErrorResponse onFailure(ErrorCode errorCode) {
return ErrorResponse.builder()
.status(errorCode.getHttpStatus().value())
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build();
}
}
์ด๋ ์ปค์คํ ์๋ฌ ๋ฆฌ์คํฐ์ค์ด๋ค.
onFaliure ํจ์๋ฅผ ํตํด ErrorCode๋ฅผ ๋ฐ์ผ๋ฉด, ๊ทธ ErrorCode๋ฅผ ์ด์ฉํด ErrorResponse๋ฅผ ๋ง๋ค๋๋ก ๋ง๋ค์๋ค.
ErrorCode๋ ๋ฐ์์ ์ค๋ช ํ๊ฒ ๋ค.
๊ทธ๋ฌ๋ฉด ์ด๋ฐ ์๋ฌ๋ฅผ ์ป์ ์ ์๋ค.
๐น๋น์ฆ๋์ค ์๋ฌ ์ฒ๋ฆฌ
๋น์ฆ๋์ค ์๋ฌ๋ RuntimeException์ ์์๋ฐ์ BaseException ์๋ฌ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ฒ๋ฆฌํ๋ค.
์ด ์ปค์คํ ์๋ฌ ๊ฐ์ฒด๋ฅผ ์์๋ฐ์ ์ฌ๋ฌ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๋น์ฆ๋์ค ์๋ฌ ์ปค์คํ ์ ์งํํ๋ค.
@Getter
@RequiredArgsConstructor
public class BaseException extends RuntimeException {
private final ErrorCode errorCode;
private final String message;
}
์ ์ฝ๋์์ ๋ณผ ์ ์๋ ErrorCode ๋ ์๋์์ ์ค๋ช ํ๊ฒ ๋ค.
@Getter
public class BadRequestException extends BaseException {
public BadRequestException() {
super(ErrorCode._BAD_REQUEST, ErrorCode._BAD_REQUEST.getMessage());
}
public BadRequestException(String message) {
super(ErrorCode._BAD_REQUEST, message);
}
public BadRequestException(ErrorCode errorCode) {
super(errorCode, errorCode.getMessage());
}
}
์ด ์๋ฌ ๊ฐ์ฒด๋ ์๋น์ค ๋ก์ง ์ค ์๋ฌ๋ฅผ ๋ฐ์์ํฌ ๋ ์ฌ์ฉํ๋ค.
ErrorCode๋ ์๋ฌ ๋ฉ์์ง๋ฅผ ๋ฐ์์ ์๋ฌ ๋ฆฌ์คํฐ์ค๋ฅผ ๋ง๋ค๋๋ก ๊ตฌ์ฑํ๋ค.
@Getter
@AllArgsConstructor
public enum ErrorCode {
/* Common */
INVALID_INPUT_VALUE(BAD_REQUEST, "C001", "Invalid Input Value"),
_INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR, "C000", "์๋ฒ ์๋ฌ, ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ ๋ฐ๋๋๋ค."),
_BAD_REQUEST(BAD_REQUEST, "C001", "์๋ชป๋ ์์ฒญ์
๋๋ค."),
_METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED, "C003", "์ง์ํ์ง ์๋ Http Method ์
๋๋ค."),
...
private final HttpStatus httpStatus;
private final String code;
private final String message;
}
์ด๊ฒ์ด ๋ฐ๋ก ErrorCode์ด๋ค.
enum์ผ๋ก ๋ง๋ค์ด์ ErrorCode.INVALID_INPUT_VALUE. ... ์ด๋ฐ ์์ผ๋ก ๊ฐ์ ์ ๊ทผํ์ฌ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค.
์ปค์คํ ํ๊ณ ์ถ์ ์๋ฌ๋ฅผ ์ด๋ฐ ์์ผ๋ก ํ๋ ํ๋ ์ ์ด์ ์๋ฌ๋ฅผ ๋์ธ ์ ์๋๋ก ํ๋ค.
'Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
<Spring> Redis ๊ธฐ๋ฐ ๊ฒ์์ด ์๋ ์์ฑ ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ (1) | 2023.10.01 |
---|---|
<Spring> AOP ๊ธฐ๋ฐ ๋ถ์ฐ๋ฝ (0) | 2023.10.01 |
<Spring> ResponseEntityExceptionHandler ์ด์ฉํ ๊ณตํต ์๋ฌ ์ฒ๋ฆฌ (0) | 2023.05.07 |
<Spring> HttpServletRequest ๊ฐ ์ฌ๋ผ์ง๋ ๋ฌธ์ (1) | 2023.05.06 |
<Spring> Success Custom Response ์ ์ฉํ๊ธฐ (0) | 2023.05.02 |