오류 처리는 프로그램을 개발하는데 있어서 매우 큰 부분을 차지한다. 오류를 예측해서 비정상적인 상황이 발생하지 않게 하는 것은 정말 중요하다.
1. @ControllerAdvice 란?
@Controller나 @RestController에서 발생한 예외를 한 곳에서 관리하고 처리할 수 있게 도와주는 어노테이션이다.
2. @ControllerAdvice 예제 코드
동일한 유형의 Error Response 반환
확장성이 용이한 Custom Exception의 사용
1) ErrorCode.class
예외에 대한 정보를 담고있는 enum class 이다.
public enum ErrorCode {
INVALID_PARAMETER(400, null, "Invalid Request Data"),
COUPON_EXPIRATION(410, "C001", "Coupon Was Expired"),
COUPON_NOT_FOUND(404, "C002", "Coupon Not Found");
private final String code;
private final String message;
private final int status;
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public int getStatus() {
return status;
}
ErrorCode(final int status, final String code, final String message) {
this.status = status;
this.message = message;
this.code = code;
}
}
2) ErrorResponse.class
예외에 대한 응답 정보를 저장하는 클래스이다.
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
public class ErrorResponse {
private LocalDateTime timestamp = LocalDateTime.now();
private String message; //예외 메시지 저장
private String code; // 예외를 세분화하기 위한 사용자 지정 코드,
private int status; // HTTP 상태 값 저장 400, 404, 500 등..
//@Valid의 Parameter 검증을 통과하지 못한 필드가 담긴다.
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonProperty("errors")
private List<CustomFieldError> customFieldErrors;
public ErrorResponse() {
}
static public ErrorResponse create() {
return new ErrorResponse();
}
public ErrorResponse code(String code) {
this.code = code;
return this;
}
public ErrorResponse status(int status) {
this.status = status;
return this;
}
public ErrorResponse message(String message) {
this.message = message;
return this;
}
public ErrorResponse errors(Errors errors) {
setCustomFieldErrors(errors.getFieldErrors());
return this;
}
/*
getter 생략
*/
//BindingResult.getFieldErrors() 메소드를 통해 전달받은 fieldErrors
public void setCustomFieldErrors(List<FieldError> fieldErrors) {
customFieldErrors = new ArrayList<>();
fieldErrors.forEach(error -> {
customFieldErrors.add(new CustomFieldError(
error.getCodes()[0],
error.getRejectedValue(),
error.getDefaultMessage()
));
});
}
//parameter 검증에 통과하지 못한 필드가 담긴 클래스이다.
public static class CustomFieldError {
private String field;
private Object value;
private String reason;
public CustomFieldError(String field, Object value, String reason) {
this.field = field;
this.value = value;
this.reason = reason;
}
public String getField() {
return field;
}
public Object getValue() {
return value;
}
public String getReason() {
return reason;
}
}
}
1) CustomException.class
쿠폰 만료 예외, 존재하지 않는 쿠폰 예외, 쿠폰 중복 참여 예외 등 여러가지 예외의 기본이 되는 클래스이다. 모든 예외 정보는 ErrorCode를 통해서 전달 받는다.
public class CustomException extends RuntimeException {
private static final long serialVersionUID = 1L;
private ErrorCode errorCode;
public CustomException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
2) CouponExpirationException.class
쿠폰이 만료 되었을 경우, 발생하는 예외 클래스이다.
public class CouponExpirationException extends CustomException {
private static final long serialVersionUID = -2116671122895194101L;
public CouponExpirationException() {
super(ErrorCode.COUPON_EXPIRATION);
}
}
3) CouponNotFoundException.class
입력한 쿠폰을 찾을 수 없을 경우, 발생하는 예외 클래스이다.
public class CouponNotFoundException extends CustomException {
private static final long serialVersionUID = -2116671122895194101L;
public CouponNotFoundException() {
super(ErrorCode.COUPON_NOT_FOUND);
}
}
4) InvalidParameterException.class
public class InvalidParameterException extends CustomException {
private static final long serialVersionUID = -2116671122895194101L;
private final Errors errors;
public InvalidParameterException(Errors errors) {
super(ErrorCode.INVALID_PARAMETER);
this.errors = errors;
}
public Errors getErrors() {
return this.errors;
}
}
@Controller
@RequestMapping(value = "/")
public class HomeController {
@GetMapping("/member")
public String memberException(@Valid Member dto, BindingResult result) {
if (result.hasErrors()) {
throw new InvalidParameterException(result);
}
return "page/home";
}
@GetMapping("/exception")
public String exceptionTest(String code) {
switch (code) {
case "1":
throw new CouponExpirationException();
case "2":
throw new CouponNotFoundException();
case "3":
int a = 3 / 0;
break;
}
return "page/home";
}
}
3. 테스트
1) GET http://localhost:8080/SpringBootException/member
MVVM 패턴은 페이지를 Model, View, ViewModel 단위로 분리해서 단위 별로 의존성을 줄이고, 화면 처리에 초점을 맞춘 개발 패턴입니다.
2. View
화면을 그리는 레이아웃을 의미합니다. (HTML, XML 등..) View는 리스트나 상세 페이지 등 동적으로 데이터가 변경되는 부분에 View Model과 Data Binding하는 코드를 삽입합니다. 그리고 View Model를 감시하다가 상태 변화가 전달되면 화면을 갱신합니다.
View는 화면을 표현하는데 집중하고, 별도의 서비스 로직은 View Model를 통해 수행합니다.
3. View Model
View에 연결할 데이터(Model)와 명령(Command)으로 구성되어 있으며, Model로 부터 변경알림을 전달 받으면 View에게 변경 알림을 전달합니다. 명령은 View의 이벤트를 정의하며, View에 이벤트가 실행되면 해당 명령을 실행합니다.
4. Model
화면 표현하는데 필요한 데이터를 관리하며 사용자가 입력한 데이터를 저장하거나, 서버로 부터 받은 데이터를 저장합니다. 데이터 변경 시 View Model에게 변경 알림을 전송합니다.