💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의
웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있
www.inflearn.com
[에러 메시지]
[1. 메시지 소스를 사용하는 방법]
public FieldError(String objectName,
String field,
@Nullable Object rejectedValue,
boolean bindingFailure,
@Nullable String[] codes,
@Nullable Object[] arguments,
@Nullable String defaultMessage)
- field : 오류 필드
- rejectedValue : 사용자가 입력한 값(거절된 값)오류 발생시 사용자 입력 값을 저장하는 필드이다.
- bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
- codes : 메시지 코드
- arguments : 메시지에서 사용하는 인자
- defaultMessage : 기본 오류 메시지
[에러 메시지를 properties로 저장하여 관리하는 방법]
//errors.properties
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
[오류 메시지 설정을 사용한 controller]
//특정 필드 예외
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, new String[]{"required.item.itemName"}, null,null));
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000,1000000}, null));
}
if (item.getQuantity() == null || item.getQuantity() > 10000) {
bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, new String[]{"max.item.quantity"}, new Object[]{9999}, null));
}
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError("item", new String[]{"totalPriceMin"}, new Object[]{10000, resultPrice}, null));
}
}
- codes에 String 배열로 오류 메시지를 넘겨준다.
- 만약 설정해둔 메시지에 범위 등을 넣어야 한다면 Object를 생성해 해당 값을 넘겨준다.
[codes를 스트링 배열로 받는 이유?]
- 처음으로 넣은 값이 메시지에 없으면 다음 대안을 찾아 넣기 위해서 스트링 배열로 받는다.
- 이때 해당하는 메시지가 없으면 오류가 나게 된다.
- ( 이때 아마도 우리는 defaultMessage를 적어 넣지 않았기 때문에 오류 페이지가 나오는 것인듯.. )
[2. FieldError와 ObjectError]
FieldError, ObjectError 다루기 어려운 이것과 어떻게 더 코드를 줄여 사용할 수 있을까?
//특정 필드 예외
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.rejectValue("itemName","required");
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.rejectValue("price", "range",new Object[]{1000,1000000},null);
}
if (item.getQuantity() == null || item.getQuantity() > 10000) {
bindingResult.rejectValue("quantity","max",new Object[]{9999},null);
}
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin",new Object[]{10000, resultPrice},null)
}
}
- properties에 있는 코드를 적지 않아도 된다 어떻게 가능할까?
- MessageCodesResolver를 이해해야 이를 확인할 수 있다.
- 이는 4에서 확인 할 예정이다.
[3. 어떻게 오류코드를 설계할 것인가?]
- 자세한 오류 메시지가 필요할 때도 있고 범용적으로 사용할 수 있는 메시지가 필요할 때도 있다.
- 단순하게 만들면 범용성이 좋아서 여러 곳에서 사용할 수 있지만 세밀한 작성은 어렵다.
- 반대로 자세하게 만들면 범용성이 떨어진다. (메시지의 코드가 늘어나는 등의 문제도 있다.)
//errors.properties
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
required = 필수 값 입니다.
range = 범위는 {0}부터 {1}까지 입니다.
max = 최대 {0}까지 허용합니다.
[메시지 우선순위]
- 오류 메시지가 객체명과 필드명을 조합한 세밀한 코드가 있다면 우선순위를 높은 우선순위로 두어 그 메시지를 출력할 수 있게 한다
- 결과적으로 message properties의 수정만으로 개발 코드의 변경없이 수정이 가능해진다.
- 아래와 같이 작동한다 보면 된다.(실제 코드 XXXX)
bindingResult.rejectValue("itemName","required");
new String[]{("required.item.itemName", "required")}
[4. 스프링을 사용한 오류 코드와 메시지 처리]
- MessageCodesResolver를 사용해 검증 오류 코드로 메시지 코드들을 생성한다.
- MessageCodesResolver 인터페이스고 DefaultMessageCodesResolver는 구현체이다.
- 주로 ObjectError, FieldError와 사용한다.
[DefaultMessageCodesResolver의 기본 메시지 생성 규칙]
[객체 오류]
다음 순서로 2가지 생성
1.: code + "." + object name
2.: code
예) 오류 코드: required, object name: item
1.: required.item
2.: required
[필드 오류]
다음 순서로 4가지 메시지 코드 생성
1.: code + "." + object name + "." + field
2.: code + "." + field
3.: code + "." + field type
4.: code
예) 오류 코드: typeMismatch, object name "user", field "age", field type: int
[필드 오류 - 타입 매칭이 제대로 이루어지지 않은 경우]
1. "typeMismatch.user.age"
2. "typeMismatch.age"
3. "typeMismatch.int"
4. "typeMismatch"
- rejectValue() , reject() 는 내부에서 MessageCodesResolver 를 사용한다.여기에서 메시지 코드들을 생성한다.
- FieldError , ObjectError 의 생성자를 보면, 오류 코드를 하나가 아니라 여러 오류 코드를 가질 수 있다.
- MessageCodesResolver 를 통해서 생성된 순서대로 오류 코드를 보관한다.
즉, MessageCodesResolver는 오류 코드를 보관해주고 이를 이용해서 다른 활동을 할 수 있게 되는 것이다.
[5. 구체적인 것부터 덜 구체적인 것까지 - MessageCodesResolver]
- MessageCodesResolver는 구체적인 것을 먼저 만들어 준 뒤, 덜 구체적인 것을 가장 나중에 만든다.
- 단순하게 공백 오류를 잡아내야 할 경우엔 ValidationUtils를 사영해서 한 줄로도 코드 구현이 가능하다
[정리]
- rejectValue() 호출
- MessageCodesResolver 를 사용해서 검증 오류 코드로 메시지 코드들을 생성
- new FieldError() 를 생성하면서 메시지 코드들을 보관
- th:erros 에서 메시지 코드들로 메시지를 순서대로 메시지에서 찾고, 노출
[6. 스프링이 직접 만든 오류 메시지 처리]
- 검증 오류 코드의 2가지
- 개발자가 직접 설정한 오류 코드 -> rejectValue()를 직접 호출한다.
- @Valid - javax(자바 표준) 검증 지원 애너테이션
- 스프링이 직접 검증 오류에 추가한 경우(주로 타입 정보가 맞지 않는다.)
- @Validated - 스프링 전용 검증 애너테이션
- 개발자가 직접 설정한 오류 코드 -> rejectValue()를 직접 호출한다.