들어가기전에...
@Getter
@AllArgsConstructor
public class GeneralException extends RuntimeException {
private BaseErrorCode code;
public ErrorReasonDTO getErrorReason() {
return this.code.getReason();
}
public ErrorReasonDTO getErrorReasonHttpStatus() {
return this.code.getReasonHttpStatus();
}
}
implementation 'org.springframework.boot:spring-boot-starter-validation'
@Slf4j
@RestControllerAdvice(annotations = {RestController.class})
public class ExceptionAdvice extends ResponseEntityExceptionHandler {
//상황 1
// ConstraintViolationException 발생시 -> @PathVariable 에 매핑되는 경로 파라미터 또는 @RequestParam 에 매핑되는 쿼리 파라미터를 검증하는데 실패했을 때 발생
//해당 타입은 DefaultHandlerExceptionResolver 에 핸들러가 선언되어 있지 않기 때문에 HTTP Status 500 으로 처리됩니다.
@ExceptionHandler
public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequest request) {
String errorMessage = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.findFirst()
.orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생"));
return handleExceptionInternalConstraint(
e,
ErrorStatus.valueOf(errorMessage),
HttpHeaders.EMPTY,
request
);
}
private ResponseEntity<Object> handleExceptionInternalConstraint(
Exception e, ErrorStatus errorCommonStatus, HttpHeaders headers, WebRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
request
);
}
//상황 2
// MethodArgumentNotValidException 발생시 -> @RequestBody 로 들어오는 객체를 검증하는데 실패했을 때 발생
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
Map<String, String> errors = new LinkedHashMap<>();
e.getBindingResult().getFieldErrors().stream()
.forEach(fieldError -> {
String fieldName = fieldError.getField();
String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse("");
errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + " " + newErrorMessage);
});
return handleExceptionInternalArgs(
e,
HttpHeaders.EMPTY,
ErrorStatus.valueOf("_BAD_REQUEST"),
request,
errors
);
}
private ResponseEntity<Object> handleExceptionInternalArgs(
Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus, WebRequest request, Map<String, String> errorArgs) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), errorArgs);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
request
);
}
@ExceptionHandler
public ResponseEntity<Object> exception(Exception e, WebRequest request) {
e.printStackTrace();
return handleExceptionInternalFalse(
e,
ErrorStatus._INTERNAL_SERVER_ERROR,
HttpHeaders.EMPTY,
ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),
request
, e.getMessage()
);
}
private ResponseEntity<Object> handleExceptionInternalFalse(
Exception e, ErrorStatus errorCommonStatus, HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), errorPoint);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
request
);
}
//GeneralException에 대한 예외처리
@ExceptionHandler(value = GeneralException.class)
public ResponseEntity<Object> handleGeneralException(GeneralException generalException, HttpServletRequest request) {
ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus();
return handleExceptionInternal(
generalException,
errorReasonHttpStatus,
null
, request
);
}
private ResponseEntity<Object> handleExceptionInternal(
Exception e, ErrorReasonDTO errorReasonDTO, HttpHeaders headers, HttpServletRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(errorReasonDTO.getCode(), errorReasonDTO.getMessage(), null);
WebRequest webRequest = new ServletWebRequest(request);
return super.handleExceptionInternal(
e,
body,
headers,
errorReasonDTO.getHttpStatus(),
webRequest
);
}
}
ConstraintViolationException vs MethodArgumentNotValidException
ConstraintViolationException 예외는 Bean Validation이 실패했을 때 발생하는데, 예를 들어 @NotNull이나 @Size 같은 제약 조건 어노테이션을 위반했을 때 발생합니다.
@PathVariable 에 매핑되는 경로 파라미터 또는 @RequestParam 에 매핑되는 쿼리 파라미터를 검증하는데 실패했을 때 발생합니다.반면, MethodArgumentNotValidException예외는 @Valid 어노테이션을 사용하여 검증한 메소드의 인자가 유효하지 않을 때 발생합니다.
@RequestBody 로 들어오는 객체를 검증하는데 실패했을 때 발생합니다.
test 핸들러 작성해줄게요
public class TempHandler extends GeneralException {
public TempHandler(BaseErrorCode code) {
super(code);
}
}
예외 dto 추가해 줄게요!
flag값에 따라 다른 응답을 반환할 수 있게 해보겠습니다!
public class TempResponse {
...
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TempExceptionDTO {
Integer flag;
}
}
public class TempConverter {
...
public static TempResponse.TempExceptionDTO toTempExceptionDTO(Integer flag) {
return TempResponse.TempExceptionDTO.builder()
.flag(flag)
.build();
}
}
public interface TempQueryService {
void CheckFlag(Integer flag);
}
@Service
@RequiredArgsConstructor
public class TempQueryServiceImpl implements TempQueryService {
@Override
public void CheckFlag(Integer flag) {
if (flag == 1) {
throw new TempHandler(ErrorStatus.TEMP_EXCEPTION);
}
}
}
예외 컨트롤러 코드 추가해줍니다!
@RestController
@RequestMapping("/api/temp")
@RequiredArgsConstructor
public class TempController {
private final TempQueryService tempQueryService;
...
@GetMapping("/exception")
public ApiResponse<TempResponse.TempExceptionDTO> getException(@RequestParam(name = "flag") Integer flag) {
tempQueryService.CheckFlag(flag);
return ApiResponse.onSuccess(TempConverter.toTempExceptionDTO(flag));
}
}