오류코드 작성법
errors.properties 파일로 오류코드를 한 곳에 모아 관리하도록 할 때 메시지를 단계별로 나누어 필요에 따라 적절히 활용할 수 있도록 할 수 있다.
메시지를 세분하는 이유?
메시지를 작성할 때 여러 곳에서 사용할 수 있도록 일반적으로 작성하면, 범용성은 좋지만, 자세한 상황을 설명해야 되는 부분에서는 전달이 어려울 수 있다. 또한 자세한 설명을 위해 메시지 자체를 세밀히 작성하면, 여러 곳에서 사용하기에는 적합하지 않다.
따라서, 메시지를 단계별로 나누어 보다 범용성으로 사용하다가, 세밀하게 작성해야 하는 경우 세밀한 내용이 적용되도록 하는 것이다.
메시지를 단계별로 사용할 때, 보다 구체적일수록 우선순위가 높다.
ex)
required < required.java.lang.string < required.item.itemName < ...
사용 예시)
'required'라고 오류 코드를 사용한다고 했을 때,
#level 1
required=필수 값입니다.
#level 2
required.item.itemName=상품 이름은 필수입니다.
- 이처럼 단계별로 설정해두고, 필수로 데이터를 입력해야하는 모든 곳에서는 'required'를 사용하고, 상대적으로 상품 이름을 입력하는 란에서는 'required.item.itemName'을 사용하여 이 메시지를 높은 우선순위로 사용한다.
이렇게 객체명과 필드명을 조합한 메시지가 있는지 우선 확인하고, 없으면 좀 더 범용적인 메시지를 선택하도록 추 가 개발을 해야겠지만, 범용성 있게 잘 개발해두면, 메시지의 추가만으로 매우 편리하게 오류 메시지를 관리할 수 있다.
스프링은 MessageCodesResolver 라는 것으로 이러한 기능을 지원한다.
MessageCodesResolver
- 검증 오류 코드로 메시지 코드들을 생성한다.
- MessageCodesResolver 인터페이스이고 DefaultMessageCodesResolver 는 기본 구현체이다.
- 주로 ObjectError, FieldError와 함께 사용된다.
DefaultMessageCodesResolver의 기본 메시지 생성 규칙
ObjectError
객체 오류의 경우 다음 순서로 2가지 생성
1. code + "." + object name
2. code
예) 오류 코드: required, 객체 이름: item 일때,
1. required.item
2. required
FieldError
필드 오류의 경우 다음 순서로 4가지 메시지 코드 생성
1. code + "." + object name + "." + field
2. code + "." + field
3. code + "." + field type
4. code
예) 오류 코드: typeMismatch, 객체 이름 : "user", 필드 명 : "age", 필드 타입: int 일때,
1. "typeMismatch.user.age"
2. "typeMismatch.age"
3. "typeMismatch.int"
4. "typeMismatch"
- 이와 같이 MessageCodesResolver 는 typeMismatch.user.age 처럼 구체적인 것을 먼저 만들어주고, required 처 럼 덜 구체적인 것을 가장 나중에 만든다.
동작방식
- BindingResult의 rejectValue() , reject() 는 내부에서 이 MessageCodesResolver를 사용한다.
- 따라서, MessageCodesResolver를 통해 ObjectError, FieldError의 메시지코드를 위의 규칙대로 생성하고, 생성된 순서대로 오류코드를 보관한다.
- 예로, Item 객체의 int price 필드가 담긴 BindingResult를 로그로 찍으면,
- codes [range.item.price, range.price, range.java.lang.Integer, range]가 나오는 것을 확인할 수 있다.
- 배열의 순서를 보면 알 수 있듯이, 구체적인 것에서 덜 구체적인 순으로 생성된 것을 확인할 수 있다.
스프링이 직접 만든 오류 메시지 처리
검증 오류 코드는 다음과 같이 2가지로 나눌 수 있다.
- 개발자가 직접 정의한 오류코드 => rejectValue()로 호출
- 스프링이 직접 검증 오류에 추가한 경우(주로 타입 정보가 맞지 않음)
스프링은 타입 오류가 발생하면 typeMismatch 라는 오류 코드를 사용한다. 이 오류 코드가 MessageCodesResolver 를 통하면서 4가지 메시지 코드를 생성한다. (=> FieldError 메시지 생성 방식 참고.)
따라서, 이 메시지 코드를 errors.properties 파일에 설정해주면 스프링에 만든 오류 메시지에 대한 처리를 할 수 있다.
Validator
스프링은 검증을 체계적으로 제공하기 위해 Validator 인터페이스를 제공한다.
public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}
- supports( ) { } : 해당 검증기를 지원하는지에 대한 여부 확인
- 만약 등록된 검증기가 여러 개일 경우, 어떤 검증기가 실행되어야 할지 구분이 필요한데 이때 사용된다.
- validate(Object object, Errors errors) : 검증 대상 객체와 BindingResult
사용 예시)
/**
* supports : 검증기를 지원하는지 체크
* validate : 실제 검증이 이루어지는 메서드
*/
@Slf4j
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
//item == clazz
//item의 자식클래스여도 해당 메서드를 통과 가능(검증 가능)하다. => 즉, 자식클래스도 검증 가능.
}
@Override
public void validate(Object target, Errors errors) {
Item item = (Item) target; //다운 캐스팅하여 사용.
//검증 로직
//1. 상품명에 글자가 없을 때
//ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "itemName", "required"); //아래와 동일. 공백 같은 단순한 기능만 제공.
if(!StringUtils.hasText(item.getItemName())){
//FieldError(객체명, 필드명, 기본메시지)
// == bindingResult.addError(new FieldError("item", "itemName",
// item.getItemName(), false, new String[]{"required.item.itemName"}, null, null ));
errors.rejectValue("itemName", "required");
}
//특정 필드가 아닌 복합 룰 검증(global errors)
//2. 가격과 수량이 null이 아니고 가격*수량이 10000원 이하일 때
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));
errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
}
}
- ValidationUtils : Empty, 공백 같은 단순한 조건의 경우, 간결하게 검증할 수 있도록 하는 코드.
WebDataBinder를 통해 Validator 사용
WebDataBinder는 스프링의 파라미터 바인딩의 역할을 해주고 검증 기능도 내부에 포함한다.
1. @InitBinder로 검증기 등록
@InitBinder
public void init(WebDataBinder dataBinder) {
log.info("init binder {}", dataBinder);
dataBinder.addValidators(itemValidator); //개발자가 스프링 빈으로 생성한 검증기를 추가
}
- WebDataBinder에 검증기를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용할 수 있다.
- @InitBinder : 이 어노테이션이 붙은 해당 컨트롤러에서만 영향을 준다.
* 모든 컨트롤러에 적용(글로벌 설정)은 @SpringBootApplication이 적용된 파일에 Validator(검증기)를 직접 등록하면 된다.
@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. 검증할 객체(@ModelAttribute) 앞에 @Validated 적용
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult
bindingResult, RedirectAttributes redirectAttributes) {
...
관련 로직
...
}
- @Validated는 검증기를 실행하라는 애노테이션
- 이 애노테이션이 붙으면 앞서 WebDataBinder에 등록한 검증기를 찾아서 실행한다. 그런데 여러 검증기를 등록한다 면 그 중에 어떤 검증기가 실행되어야 할지 구분이 필요하다. 이때 supports() 가 사용된다.
*참고 : @Validated 는 '스프링 전용 검증 애노테이션'이고, @Valid 는 '자바 표준 검증 애노테이션'이다.
'Spring > SpringMVC' 카테고리의 다른 글
쿠키, 세션을 활용한 로그인, 로그아웃 (0) | 2024.02.04 |
---|---|
검증 - Bean Validation (0) | 2024.01.22 |
검증 - Validation (1) | 2024.01.15 |
메시지와 국제화 (1) | 2024.01.11 |
스프링 MVC 기본 기능 2 (0) | 2023.12.18 |