du.study기록공간

[Spring MVC] @Validated(@Valid) and BindingResult 본문

스프링

[Spring MVC] @Validated(@Valid) and BindingResult

du.study 2019. 11. 12. 01:22
728x90

이번에는 모델의 검증작업, 모델 바인딩 과정에서 발생되는 에러의 결과를 저장해주는 BindingResult에 대해서 기록하려 합니다.

 

만약 이런 해당사항의 컨트롤러,도메인이 있다고 가정하겠습니다.

@GetMapping("/success-test")
@ResponseBody
public String errorBindTest(@Validated TestDomain domain , BindingResult bindingResult){
    if(bindingResult.hasErrors()){
        return bindingResult.getAllErrors().get(0).getDefaultMessage();
    }
    return "success";
}

@GetMapping("/error-test")
@ResponseBody
public String errorTest(@Validated TestDomain domain){
    return "success";
}



public class TestDomain {
    @NotEmpty
    private String name;
    private Integer id;
    
    .......

 

현재 TestDomain 에는 name field에는 @NotEmpty가 붙어있습니다. 여기서 다음과 같이 postman(API를 테스트하기 좋은 툴? 플렛폼(협업기능이 있으니까..?)) 을 이용해서 /success-test 와 /error-test를 name값 없이 호출을 하면 다음과 같은 결과가 나오게 됩니다.

 

success-test를 호출했을 때

 

error-test를 호출했을때

두 컨트롤러의 차이점은 BindingResult에 있습니다. Controller에서 BindingResult를 생성하게 되면, 해당 핸들러를 호출하기전 바인딩 작업중 일어난 에러들, 검증과정에서 발생한 에러들을 전부 BindingResult에 담아서 컨트롤러에 전달이 됩니다. 만약 BindResult를 선언하지 않게되면 그대로 에러를 뱉어냅니다.

 

그럼이제 이 과정이 어떻게 이뤄지는지 알아보겠습니다.

 

 

가장 먼저! 컨트롤러의 Valid 작업을 진행하는 Bean을 생성하는 부분부터 기록해 보겠습니다. 이전 글에서 SpringMVC를 세팅하면서 @EnableWebMvc를 선언한 적이 있습니다. 여기서 호출이되는 WebMvcConfigurationSupport 클레스에서 해당 부분을 통해 Validator 빈 등록이 됩니다.

@Bean
public Validator mvcValidator() {
	Validator validator = getValidator();
	if (validator == null) {
		if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
        	Class<?> clazz;
			try {
				String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
				clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
			}
			catch (ClassNotFoundException | LinkageError ex) {
				throw new BeanInitializationException("Failed to resolve default validator class", ex);
			}
			validator = (Validator) BeanUtils.instantiateClass(clazz);
		}
		else {
			validator = new NoOpValidator();
		}
	}
	return validator;
}

 

해당 과정에서 다음 페키지 파일이 있는지 확인하고 Bean으로 등록해줍니다.

1. "javax.validation.Validator", 

2. "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean"

해당 패키지를 주입해주기 위해서 hibernate-validator, validation-api maven dependency를 추가해 주었습니다.

 

Bean 생성 후, 컨트롤러를 호출하여 위에서 확인했던 success-test의 경우를 확인해 보겠습니다.

 

API를 호출하게 되면 RequestMappingHandlerAdapter 어뎁터 안에서 Controller를 doInvoke하기전에 getMethodArgumentValues 안에서 핸들러 메서드를 위한 여러가지의 작업을 진행해 주는데 그 중 Validate작업이 여기에 포함이 됩니다.

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	return doInvoke(args);
}

이 과정을 통해 HandlerMethodArgumentResolverComposite 클레스에서 HandlerMethodArgumentResolver 를 찾아오는 작업을 진행하고, 이에 맞는 HandlerMethodArgumentResolver 를 가져와서 resolveArgument 작업을 해주게 됩니다.

 

이때, api 콜의 결과로 해당 부분은 ModelAttributeMethodProcessor (HandlerMethodArgumentResolver)를 사용하게 되며 해당 클레스의 resolveArgument 에서는 모델의 유효성 검사 또는 바인딩 작업등을 진행해주게 됩니다.

 

 

 

그리고 원래 찾아보려 했던 메인부분을 보면 다음과 같이 코드가 되어있습니다.

validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
	throw new BindException(binder.getBindingResult());
}

validateIfApplicable 해당부분에서 binder가 가지고 있던 validate를 호출하게 되면서 binder에 Validate결과를 저장하게 됩니다. 이후에 핸들러에 BindingResult가 존재한다면, 해당 에러부분을 거치지 않고 다음과 같은 코드를 통해서 request로 전달되게 됩니다.

Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);

만약 핸들러에 BindingResult가 없는경우, 즉 위에서 호출했었던 /error-test의 경우에는 throw new BindException에 걸려서 에러를 리턴하게 됩니다.

 

 

 

 

중간중간 생략된 부분이 조금 있지만 클레스 하나씩 다보면 진짜 끝이 없기에.. 중요하다 생각이 들었던 포인트만 우선 기록하고 마무리합니다.

 

728x90
Comments