1) Spring에서 제공하는 BindingResult를 사용해서 Error을 등록하고 사용할 수 있다.
2) 또한 타임리프와 연동해서 사용이 가능하다.
참고
1) 에러를 담당할 객체 다음에 매개변수로 BindingResult가 와야 된다.
public String addItemV3( @ModelAttribute Item item, BindingResult bindingResult){2) 자동으로 Model에 등록 된다.
3) message , 국제화 기능을 사용할 수 있다.
4) 데이터 바인딩 에러 발생의 경우
데이터 형변환의 경우에는 Controller에 접근하지 못하고 Error가 발생함으로 Spring에서 직접 BindingResult에 messageCode를 만든 FieldError을 담아준다. (아래 설명)
1. 간편화 등록 - reject(글로벌에러) , rejectValue(필드에러)
fieldError , ObjectError 등록을 간편화한 구조이다.
1. rejectvalue
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
1) field : 오류 코드
2) errorCode : 오류 필드명
3) errorArgs : 오류 메시지에서 {0} 을 치환하기 위한 값
4) defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지//field bindingResult.rejectValue("price","range", new Object[]{1000,1000000} , null); //클로벌 (errorCode만 오류 코드가 빠진다.) bindingResult.reject("totalPriceMin" , new Object[]{10000,resultPrice} , null);reject() , rejectValue를 사용 시 messageCodesResolver()가 작동되어서 message 코드를 등록해준다. (아래 설명)
2. bindingResult.addError()
1) 매개변수 new FieldError(필드에러) , new ObjectError(글로벌 에러)를 통해 필드 or 글로벌 에러를 등록하고 사용이 가능하다
2) 에러가 발생한 곳에 해당 함수를 사용해서 사용할 수 있다.2개의 오버로딩 제공
- public FieldError(String objectName, String field, String defaultMessage);
1-1) objectName : 객체명
1-2) field : 오류가 발생한 필드명
1-3) defaultMessage : 오류 메세지bindingResult.addError(new FieldError("item","quantity","수량은 최대 9,999 까지 허용합니다"));
- public FieldError(String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable
Object[] arguments, @Nullable String defaultMessage)
1) objectName : 오류가 발생한 객체 이름
2) field : 오류 필드
3) rejectedValue : 사용자가 입력한 값(거절된 값)
4) bindingFailure : : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
5) codes : 메시지 코드 - message 기능 사용
6) argument : 메시지에서 사용하는 인자
7) defaultMessage : 기본 오류 메시지///case - 1 (message 사용 안함) bindingResult.addError(new FieldError("item","quantity", item.getQuantity(), false , null , null ,"수량은 최대 9,999 까지 허용합니다")); /// case - 2 (message 사용) bindingResult.addError(new FieldError("item","itemName", item.getItemName(), false , new String[]{"required.item.itemName"} , null ,null));
1. @Validate를 사용한다.
1) InitBinder에 해당하는 검증 객체를 등록 후
@InitBinder public void init(WebDataBinder dataBinder){ dataBinder.addValidators(itemValidator); }2) 해당하는 객체 앞에 @Validated를 설정한다.
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes)글로벌 설정
} @SpringBootApplication public class ItemServiceApplication implements WebMvcConfigurer { public static void main(String[] args) { SpringApplication.run(ItemServiceApplication.class, args); } @Override public Validator getValidator() { return new ItemValidator(); }해당처럼 설정하면 모든 컨트롤러에 적용된다.
2. implements Validator를 할당받은 객체를 직접 선언
3. Validator 설명
public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); }1) supports() {} : 해당 검증기를 지원하는 여부 확인(뒤에서 설명)
2) validate(Object target, Errors errors) :
: 검증 대상 객체와 BindingResult
ex) 구현 예시 코드@Override public boolean supports(Class<?> clazz) { return Item.class.isAssignableFrom(clazz); //넘어서는 클래스가 Item의 자식이거나 Item 자체냐? } //Errors BindingResult의 부모클래스 @Override public void validate(Object target, Errors errors) { Item item = (Item) target; //Null , 공백 , 길이 체크 if(!StringUtils.hasText(item.getItemName())){ //errors.addError(new FieldError("item","itemName", item.getItemName(), false , new String[]{"required.item.itemName"} , null ,null)); errors.rejectValue("itemName","required" ); // Code 구현 : errorCode.가르키는Object.field } }
매우 일반적인 로직 검증에는 Spring에서 제공하는 BeanValidation을 적용할 수 있다.
예시 코드public class Item { private Long id; @NotBlank private String itemName; @NotNull @Range(min = 1000, max = 1000000) private Integer price; @NotNull @Max(9999)private Integer quantity; }1. 동작원리
LocalValidatorFactoryBean을 글로벌 Validator로 등록한다. 이 Validator는 보고 검증을 수행한다. 이렇게 글로벌 Validator가 적용되어 있기 때문에 @Validated , @Valid , @NotNull만 적용하면 된다.검증 오류가 발생하면, FieldError , ObjectError같은 애노테이션을 생성해서 BindingResult 에 담아준다.
참고사항
1) 직접 글로벌 Validator을 등록하면 LocalValidatorFactoryBean을 등록하지 않기 때문에 주의하자
2) 검증 시 @Validated , @Valid 둘 다 사용 가능하지만, @Validated에는 group 추가 기능이 있다
3) 글로벌 오류 일 경우에는 BeanValidation보다는 자바코드로 직접 사용을 권장한다.2-1 검증 순서 - @ModelAttribute
1.@ModelAttribute 각각의 필드에 타입 변환 시도
1-1) 성공하면 다음으로
1-2) 실패 시 typeMismatch로 FieldError() 추가
2. Validator 적용2-2 검증 순서 - @RequestBody
- @RequestBody를 사용하는 경우, BindingResult를 지원하지 않는다.
why? @RequestBody에서 객체 바인딩과 검증 실패가 발생하면, Spring은 바로 예외를 던지기 때문이다.- @RequestBody와 @Valid를 사용할 때 검증 실패 시
1) Spring이 객체 매핑 후 Bean Validation을 수행합니다.
2) Bean Validation 실패 시 MethodArgumentNotValidException이 발생합니다.
3) 컨트롤러 메서드로 진입하지 않고 예외 처리기로 전달됩니다.3. BeanValidation에 message 사용
ex) item 객체에 itemName에서 에러 발생 시
@NotBlank
1. NotBlank.item.itemName
2. NotBlank.itemName
3. NotBlank.java.lang.String
4. NotBlank4. 에러 메세지 적용 순서
- MessageSource에서 적용된 에러 찾기
- 속성에 있는 message 값 사용
- 라이브러리 제공 기본값 사용
5. 하나의 객체에 BeanValidation을 다르게 적용하는 방법
1. 도메인을 요구 사항에 따라서 분리하고 검증 Bean을 적용 시킨다..
BeanValidation으로 등록한 검증과 직접 등록한 검증이 중복 동작 될 수 있음으로 주의하자!
- Global Error의 경우 = 직접 자바코드로 new ObjectError or reject를 구현
- Field Error의 경우
2-1) BeanValidation을 사용한다.
2-2) 안될 경우 직접 자바코드로 등록한다.
1. ObjectError()에러 확인
- th:if="${#fields.hasGlobalErrors()}“ : GlobalError 확인
- th:each를 통한 모든 글로벌 에러 추출
- #fields로 `BindingResult가 제공하는 검증 오류에 접근할 수 있다.
<div th:if="${#fields.hasGlobalErrors()}"> <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p> </div>2. FieldError()에러 확인
- th:errors="*{itemName}“
해당 에러는 th:if , th:text를 생략해준 문장<input type="text" id="itemName" th:field="*{itemName}" th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요"> <div class="field-error" th:errors="*{itemName}">상품명 오류</div>
매개변수로 메세지를 등록하는 코드이며 주로 reject(), rejectvalue() 내부에서 해당 함수가 사용되어 messageCode를 등록한다.
void MessageCodesFields(){ String[] name = messageCodesResolver.resolveMessageCodes("required", "item" , "itemName", String.class); Assertions.assertThat(name).containsExactly("required.item.itemName","required.itemName","required.java.lang.String","required"); }
- 필드의 경우
1.: errorcode + "." + object name + "." + field = required.item.itemName
2.: errorcode + "." + field
3.: errorcode + "." + field type
4.: errorcode- 글로벌의 경우
1.: code + "." + object name
2.: code
- typeMismatch.item(객체).price(필드명)
- typeMismatch.price(필드명)
- typeMismatch.java.lang.Integer(필드 타입)
- typeMismatch
를 BindingResult에 담아서 반환해준다.