Spring

[Spring] 스프링 MVC(8) - Validator와 Errors/BindingResult

재담 2022. 3. 14. 23:29

Validator와 Errors/BindingResult를 이용한 객체 검증

org.springframework.validation.Validator 인터페이스를 사용하면, 스프링이 제공하는 객체 검증 및 에러 메시지 지원 등의 기능을 사용할 수 있다. 컨트롤러에서 커맨드 객체의 값을 검증할 때 특히 Validator를 유용하게 사용할 수 있다. 다음 예시를 보자.

public class MemberRegistValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return MemberRegistRequest.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        MemberRegistRequest regReq = (MemberRegistRequest) target;
        if (regReq.getEmail() == null || regReq.getEmail().trim().isEmpty()) {
            errors.rejectValue("email", "required");
        }

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword", "required");

        if (regReq.hasPassword()) {
            if (regReq.getPassword().length() < 5) {
                errors.rejectValue("password", "shortPassword");
            } else if (!regReq.isSamePasswordConfirmPassword()) {
                errors.rejectValue("confirmPassword", "notSame");
            }
        }
        Address address = regReq.getAddress();
        if (address == null) {
            errors.rejectValue("address", "required");
        } else {
            errors.pushNestedPath("address");
            try {
                ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address1", "required");
                ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address2", "required");
            } finally {
                errors.popNestedPath();
            }
        }
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "birthday", "required");
    }
}

위 코드에서 supports() 메서드는 Validator가 해당 타입의 객체를 지원하지 여부를 리턴한다. 실제 값을 검증하는 코드는 validate() 메서드에 위치한다. validate() 메서드의 target 파라미터는 값을 검증할 객체이며, errors 파라미터는 값이 올바르지 않을 경우 그 내용을 저장하기 위해 사용된다.

 

ValidationUtils 클래스는 코드를 간결하게 만들어준다. 예를 들어, 다음 코드는 target 객체의 "name" 프로퍼티 값이 null이거나 길이가 0인 경우 errors 객체에 "name" 프로퍼티의 에러 코드로 "required"를 등록한다.

ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");

 

컨트롤러에서는 다음과 같이 커맨드 객체를 검증할 수 있다.

@Controller
@RequestMapping("/member/regist")
public class RegistrationController {
    @RequestMapping(method = RequestMethod.POST)
    public String regist(
            @ModelAttribute("memberInfo") MemberRegistRequest memRegReq,
            BindingResult bindingResult) {
        new MemberRegistValidator().validate(memRegReq, bindingResult);
        
        if (bindingResult.hasErrors()) {
            return MEMBER_REGISTRATION_FORM;
        }
        
        memberService.registNewMember(memRegReq);
        return "member/registered";
    }
    
    // 이하 생략
    ...
}

위 코드에서 regist() 메서드는 커맨드 객체인 memReqReg 파라미터와 에러 정보를 보관할 bindingResult 파라미터를 갖고 있다. BindingResult는 Errors 인터페이스를 상속받은 타입으로 컨트롤러를 구현할 때는 두 타입 중 하나를 사용하면 된다.

 

커맨드 객체를 검증하려면 MemberRegistValidator 객체를 생성하고 validate() 메서드를 호출하면 된다. validate() 메서드를 실행한 후에 값에 오류가 존재하면 bindingResult.hasErrors() 메서드는 true를 리턴한다. 다시 말해 Validator에서 Errors의 reject 메서드를 한 번 이상 호출하면 Errors.hasErrors() 메서드는 true를 리턴하게 된다.

 

참고로 커맨드 객체의 에러 정보를 담기 위해 사용되는 Erros 파라미터나 BindingResult 파라미터는 반드시 커맨드 객체 파라미터 바로 뒤에 위치해야 한다.

 

Errors와 BindingResult 인터페이스의 주요 메서드

Errors 인터페이스의 주요 메서드는 다음과 같다.

  • reject(String errorCode) : 전체 객체에 대한 글로벌 에러 코드를 추가한다.
  • reject(String errorCode, String defaultMessage) : 에러 코드에 대한 메시지가 존재하지 않을 경우 defaultMessage를 사용한다.
  • reject(String errorCode, Object[] errorArgs, String defaultMessage) : 메시지 인자로 errorArgs를 전달한다.
  • rejectValue(String field, String errorCode) : 필드에 대한 에러 코드를 추가한다.
  • rejectValue(String field, String errorCode, String defaultMessage) : 에러 코드에 대한 메시지가 존재하지 않을 경우 defaultMessage를 사용한다.
  • rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) : 메시지 인자로 errorArgs를 전달한다.

 

에러가 발생했는지의 여부를 확인할 수 있는 메서드는 다음과 같다.

  • boolean hasErrors() : 에러가 존재하면 true를 리턴한다.
  • int getErrorCount() : 에러 개수를 리턴한다.
  • boolean hasGlobalErrors() : reject() 메서드를 이용해서 추가된 글로벌 에러가 존재할 경우 true를 리턴한다.
  • int getGlobalErrorCount() : reject() 메서드를 이용해서 추가된 글로벌 에러 개수를 리턴한다.
  • boolean hasFiledErrors() : rejectValue() 메서드를 이용해서 추가된 에러가 존재할 경우 true를 리턴한다.
  • int getFieldErrorCount() : rejectValue() 메서드를 이용해서 추가된 에러 개수를 리턴한다.
  • boolean hasFieldErrors(String field) : rejectValue() 메서드를 이용해서 추가된 특정 필드의 에러가 존재할 경우 true를 리턴한다.
  • int getFieldErrorCount(String field) : rejectValue() 메서드를 이용해서 추가된 특정 필드의 에러 개수를 리턴한다.

 

BindingResult 인터페이스의 주요 메서드는 다음과 같다.

  • Object getTarget() : 검사 대상이 되는 객체를 구한다.
  • String[] resolveMessageCodes(String errorCode) : 에러 코드를 메시지 코드로 변환한다.
  • String[] resolveMessageCodes(String errorCode, String field) : 에러 코드를 field에 해당하는 메시지 코드로 변환한다.

 

 


Reference

  • 웹 개발자를 위한 Spring 4.0 프로그래밍 (최범균 저)