Java Bean Validation, Spring

tokkaiiii·2025년 6월 19일

spring-mvc

목록 보기
27/27

초기화

ValidationAutoConfiguration

//  jakarta.validation; 의 Validator 클래스가 Bean으로 등록되어 있지 않으면 실행된다
// defaultValidator 메소드에서
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();

LocalValidatorFactoryBean

  • ValidatorFactory를 구현하고 있다
  • SpringValidatorAdapter를 상속하고 있다

LocalValidatorFactoryBean의 아래 메소드 실행

// afterPropertiesSet 메소드
// validator를 만들기 위해 팩토리 가져옴
this.validatorFactory = configuration.buildValidatorFactory();

validatorFactory 를 가져오는 메소드는 AbstractConfigurationImpl

List<ValidationProvider<?>> providers = providerResolver.getValidationProviders();
					assert providers.size() != 0; // I run therefore I am
					factory = providers.get( 0 ).buildValidatorFactory( this );
     
// hibernate에 있는 validator
@Override
public ValidatorFactory buildValidatorFactory(ConfigurationState configurationState) {
	return new ValidatorFactoryImpl( configurationState );
}

팩토리에서 검증에 수행할 validator를 가져온다

setTargetValidator(this.validatorFactory.getValidator());

ValidatorFactoryImpl 클래스

@Override
public Validator getValidator() {
	return createValidator(
			constraintCreationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory(),
			constraintCreationContext,
			validatorFactoryScopedContext,
			methodValidationConfiguration
	);
}

내부 메소드인 createValidator

	Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory,
			ConstraintCreationContext constraintCreationContext,
			ValidatorFactoryScopedContext validatorFactoryScopedContext,
			MethodValidationConfiguration methodValidationConfiguration) {
		BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent(
				new BeanMetaDataManagerKey( validatorFactoryScopedContext.getParameterNameProvider(), constraintCreationContext.getValueExtractorManager(), methodValidationConfiguration ),
				key -> new BeanMetaDataManagerImpl(
						constraintCreationContext,
						executableHelper,
						validatorFactoryScopedContext.getParameterNameProvider(),
						javaBeanHelper,
						beanMetadataClassNormalizer,
						validationOrderGenerator,
						buildMetaDataProviders(),
						methodValidationConfiguration
				)
		);

		return new ValidatorImpl(
				constraintValidatorFactory,
				beanMetaDataManager,
				constraintCreationContext.getValueExtractorManager(),
				constraintCreationContext.getConstraintValidatorManager(),
				validationOrderGenerator,
				validatorFactoryScopedContext
		);
	}

결국 validatorFactory로 ValidatorImpl 객체를 가져온다, 이것 또한 hibernate
SpringValidatorAdapter 클래스의 targetValidator에 ValidatorImpl을 넣는다

WebMvcAutoConfiguration

@Bean
@Override
public Validator mvcValidator() {
	if (!ClassUtils.isPresent("jakarta.validation.Validator", getClass().getClassLoader())) {
		return super.mvcValidator();
	}
	return ValidatorAdapter.get(getApplicationContext(), getValidator());
}
public static Validator get(ApplicationContext applicationContext, Validator validator) {
		if (validator != null) {
			return wrap(validator, false);
		}
		return getExistingOrCreate(applicationContext);
	}

	private static Validator getExistingOrCreate(ApplicationContext applicationContext) {
		Validator existing = getExisting(applicationContext);
		if (existing != null) {
			return wrap(existing, true);
		}
		return create(applicationContext);
	}

	private static Validator getExisting(ApplicationContext applicationContext) {
		try {
			jakarta.validation.Validator validatorBean = applicationContext.getBean(jakarta.validation.Validator.class);
			if (validatorBean instanceof Validator validator) {
            // LocalValidatorFactoryBean이 들어옴
				return validator;
			}
			return new SpringValidatorAdapter(validatorBean);
		}
		catch (NoSuchBeanDefinitionException ex) {
			return null;
		}
	}

Validator를 한 번 감싼다

private static Validator wrap(Validator validator, boolean existingBean) {
		if (validator instanceof jakarta.validation.Validator jakartaValidator) {
			if (jakartaValidator instanceof SpringValidatorAdapter adapter) {
				return new ValidatorAdapter(adapter, existingBean);
			}
			return new ValidatorAdapter(new SpringValidatorAdapter(jakartaValidator), existingBean);
		}
		return validator;
	}

WebMvcConfigurationSupport

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcValidator") Validator validator) {
	RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
		adapter.setContentNegotiationManager(contentNegotiationManager);
		adapter.setMessageConverters(getMessageConverters());
		// 바인딩을 위한 초기 작업이 이루어지는 곳
        adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));

바인딩하는 와중에 검증이 이루어져야 하기 때문에 databinder에 검증기 설정한다
바인딩 초기화 작업에 들어가는 validator는 ValidatorAdapter다

protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
			FormattingConversionService mvcConversionService, Validator mvcValidator) {

		ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
		initializer.setConversionService(mvcConversionService);
		initializer.setValidator(mvcValidator);
		MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
		if (messageCodesResolver != null) {
			initializer.setMessageCodesResolver(messageCodesResolver);
		}
		return initializer;
	}

RequestMappingHandlerAdapter에서 setWebBindingInitializer 메소드로 webBindingInitializer를 넣는다
webBindingInitializer 여기엔 ValidatorAdapter 있음

여기까지가 세팅

HTTP 요청했을 경우

RequestMappingHandlerAdaper클래스 invokeHandlerMethod

// factory 생성
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		Class<?> handlerType = handlerMethod.getBeanType();
		Set<Method> methods = this.initBinderCache.get(handlerType);
		if (methods == null) {
			methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
			this.initBinderCache.put(handlerType, methods);
		}
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
		// Global methods first
		this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
			if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
				Object bean = controllerAdviceBean.resolveBean();
				for (Method method : methodSet) {
					initBinderMethods.add(createInitBinderMethod(bean, method));
				}
			}
		});
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
		DefaultDataBinderFactory factory = createDataBinderFactory(initBinderMethods);
		factory.setMethodValidationApplicable(this.methodValidator != null && handlerMethod.shouldValidateArguments());
		return factory;
	}

구현체로 ServletRequestDataBinderFactory 생성

protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
			throws Exception {
// 저장했던 WebBindingInitializer 가져옴
		return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
	}

팩토리 클래스에 validator 저장해둠
ModelAttributeMethodProcessor 클래스 resolveArgument 메소드에서 binderFactory로 WebDataBinder 만든다

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, type);
	private WebDataBinder createBinderInternal(
			NativeWebRequest webRequest, @Nullable Object target, String objectName,
			@Nullable ResolvableType type) throws Exception {

		WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
		dataBinder.setNameResolver(new BindParamNameResolver());

		if (target == null && type != null) {
			dataBinder.setTargetType(type);
		}

		if (this.initializer != null) {
			this.initializer.initBinder(dataBinder);
		}
	@Override
	public void initBinder(WebDataBinder binder) {
		binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
		if (this.directFieldAccess) {
			binder.initDirectFieldAccess();
		}
		if (this.declarativeBinding != null) {
			binder.setDeclarativeBinding(this.declarativeBinding);
		}
		if (this.messageCodesResolver != null) {
			binder.setMessageCodesResolver(this.messageCodesResolver);
		}
		if (this.bindingErrorProcessor != null) {
			binder.setBindingErrorProcessor(this.bindingErrorProcessor);
		}
		if (this.validator != null) {
			Class<?> type = getTargetType(binder);
			if (type != null && this.validator.supports(type)) {
				binder.setValidator(this.validator);
			}
		}
		if (this.conversionService != null) {
			binder.setConversionService(this.conversionService);
		}
		if (this.propertyEditorRegistrars != null) {
			for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
				propertyEditorRegistrar.registerCustomEditors(binder);
			}
		}
	}

바인더가 만들어지면 검증 시도

	validateIfApplicable(binder, parameter);

파라미터에 Valid나 Validated 어노테이션 선언된지 본다

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		for (Annotation ann : parameter.getParameterAnnotations()) {
			Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
			if (validationHints != null) {
				binder.validate(validationHints);
				break;
			}
		}
	}

DataBinder에서 검증

	public void validate(Object... validationHints) {
    // 타켓 가져오고
		Object target = getTarget();
		Assert.state(target != null, "No target to validate");
        // 오류 저장할 BindingResult 가져오고
		BindingResult bindingResult = getBindingResult();
		// Call each validator with the same binding result
		for (Validator validator : getValidatorsToApply()) {
			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
				smartValidator.validate(target, bindingResult, validationHints);
			}
			else if (validator != null) {
            // 검증 수행
				validator.validate(target, bindingResult);
			}
		}
	}

여기서 부턴 hibernate로 넘어감

@Override
	public void validate(Object target, Errors errors) {
		if (this.targetValidator != null) {
			processConstraintViolations(this.targetValidator.validate(target), errors);
		}
	}

ValidatorImpl

	@Override
	public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
		Contracts.assertNotNull( object, MESSAGES.validatedObjectMustNotBeNull() );
		sanityCheckGroups( groups );

		@SuppressWarnings("unchecked")
		Class<T> rootBeanClass = (Class<T>) object.getClass();
		BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass );

		if ( !rootBeanMetaData.hasConstraints() ) {
			return Collections.emptySet();
		}

		BaseBeanValidationContext<T> validationContext = getValidationContextBuilder().forValidate( rootBeanClass, rootBeanMetaData, object );

		ValidationOrder validationOrder = determineGroupValidationOrder( groups );
		BeanValueContext<?, Object> valueContext = ValueContexts.getLocalExecutionContextForBean(
				validatorScopedContext.getParameterNameProvider(),
				object,
				validationContext.getRootBeanMetaData(),
				PathImpl.createRootPath()
		);

		return validateInContext( validationContext, valueContext, validationOrder );
	}
profile
풀스택 자바 개발자입니다

0개의 댓글