// jakarta.validation; 의 Validator 클래스가 Bean으로 등록되어 있지 않으면 실행된다
// defaultValidator 메소드에서
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
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을 넣는다
@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;
}
@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 있음
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 );
}