0
点赞
收藏
分享

微信扫一扫

Hibernate Validator实现方式和源码重点部分解读

zhyuzh3d 2022-03-30 阅读 80

Hibernate Validator实现方式和源码重点部分解读

Hibernate Validator实现方式和源码重点部分解

1.抛砖引玉

最近在项目中需要对Controller中的参数进行校验,故使用了Hibernate Validator进行检验,开始的时候发现配置无效,出于好奇,对@Validator注解是如何校验的进行了源码的部分解读,以下只对Controller层参数校验原理分享出自己个人的理解。
先上问题,经过分析,发现是没有创建本地的异常处理类,导致接口返回不了主键中的校验信息,而是直接返回了Spring 的Bad Request 400错误,而我们希望的是返回@Empty(message=“XXXXX”)中的信息。
最终的解决方式如下,创建了全局异常处理类来接收Validator返回的异常,并进行处理。

@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler({BindException.class, ConstraintViolationException.class})
    public RspDataEntity<String> validatorExceptionHandler(Exception e) {

        String msg = e instanceof BindException ? msgConvertor(((BindException) e).getBindingResult())
                : msgConvertor(((ConstraintViolationException) e).getConstraintViolations());

        return RspDataEntity.fail().code(400).msg(msg).build();
    }

    public static String msgConvertor(BindingResult bindingResult) {
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        fieldErrors.forEach(fieldError -> sb.append(fieldError.getDefaultMessage()).append(","));

        return sb.deleteCharAt(sb.length() - 1).toString().toLowerCase();
    }

    private String msgConvertor(Set<ConstraintViolation<?>> constraintViolations) {
        StringBuilder sb = new StringBuilder();
        constraintViolations.forEach(violation -> sb.append(violation.getMessage()).append(","));

        return sb.deleteCharAt(sb.length() - 1).toString().toLowerCase();
    }
}

其他配置如下
在这里插入图片描述
在这里插入图片描述
请求返回结果如下
在这里插入图片描述

2.Validator初始化

springboot 在启动的过程中,ApplicationListener在耗时任务的后台线程中触发早期初始化,其中就包括Validator,如下面代码中的**performPreinitialization()**方法

@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
	public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";

	private static final AtomicBoolean preinitializationStarted = new AtomicBoolean();

	private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);

	private static final boolean ENABLED;

	@Override
	public void onApplicationEvent(SpringApplicationEvent event) {
		if (!ENABLED) {
			return;
		}
		if (event instanceof ApplicationEnvironmentPreparedEvent
				&& preinitializationStarted.compareAndSet(false, true)) {
			performPreinitialization();
		}
		if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
				&& preinitializationStarted.get()) {
			try {
				preinitializationComplete.await();
			}
			catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}
		}
	}

	private void performPreinitialization() {
		try {
			Thread thread = new Thread(new Runnable() {

				@Override
				public void run() {
					runSafely(new ConversionServiceInitializer());
					runSafely(new ValidationInitializer());
					runSafely(new MessageConverterInitializer());
					runSafely(new JacksonInitializer());
					runSafely(new CharsetInitializer());
					preinitializationComplete.countDown();
				}

				public void runSafely(Runnable runnable) {
					try {
						runnable.run();
					}
					catch (Throwable ex) {
						// Ignore
					}
				}

			}, "background-preinit");
			thread.start();
		}
		catch (Exception ex) {
			preinitializationComplete.countDown();
		}
	}

在这里插入图片描述

通过断点也可以发现,当监听的事件为ApplicationEnvironmentPreparedEvent事件,并通过CAS自旋的方式获取到锁之后,进行一系列的初始化,包括validator检验器,messageConverter消息处理器等初始化,由于项目集成了Hibernate Validator框架,因此下面代码在初始化的时候,通过工厂模式获取到的Validator为Hibernate Validator中的ValidatorImpl,感兴趣的朋友可以通过断点查看如何获取到的。
在这里插入图片描述
至此,Validator的初始化就完成了。

3.自动装配

3.1 ValidationAutoConfiguration

初始化后,springboot会对Validator校验器进行自动装配,这里主要涉及两个自动装配的bean,分为别为WebMvcAutoConfiguration 以及 ValidationAutoConfiguration具体实现如下图

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	@ConditionalOnMissingBean(Validator.class)
	public static LocalValidatorFactoryBean defaultValidator() {
		LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
		MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
		factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
		return factoryBean;
	}

这里重点关注defaultValidator方法,返回LocalValidatorFactoryBean类型的工厂Bean,改工厂Bean继承了InitializingBean,因此,在实例化的过程我们可以重点关注LocalValidatorFactoryBean中的afterPropertiesSet,它在bean实例化的过程中对LocalValidatorFactoryBean进行真正的配置。

@Override
	@SuppressWarnings({"rawtypes", "unchecked"})
	public void afterPropertiesSet() {
		Configuration<?> configuration;
		//此处省略了其他配置,感兴趣可以自己阅读源码
		
		//将配置类传入bean的增强器,对bean进行配置
		postProcessConfiguration(configuration);

		//下面是重点部分,这段代码应该似曾相识,初始化的时候曾将javax.validation.validator 初始化为Hibernate Validator中的ValidatorImpl,而这里就是将ValidatorImpl赋值给targetValidator,即我们要采用的最终的validator
		try {
			this.validatorFactory = configuration.buildValidatorFactory();
			setTargetValidator(this.validatorFactory.getValidator());
		}
		finally {
			closeMappingStreams(mappingStreams);
		}
	}

上面代码是重点部分,这段代码应该似曾相识,上面提到过spring在初始化的时候曾将javax.validation.validator 初始化为Hibernate Validator中的ValidatorImpl,而这里就是将ValidatorImpl赋值给targetValidator,这里又用到了适配器模式,通过ValidatorAdapter来进行校验处理,即我们要采用的最终的validator为Hibernate Validator中的ValidatorImpl

3.2 WebMvcAutoConfiguration

因为,我们要对controller请求的参数进行校验,Validator装配好之后,是如何与Web请求进行关联的呢,就要重点关注WebMvcAutoConfiguration,它里面主要是对MVC的一些配置,包括处理器适配器啊,视图解析器啊等等。其中重点关注,请求的处理器适配器RequestMappingHandlerAdapter(正是它负责处理Controller层注解@RequestMapping 修饰的方法请求调用)

   // =================== 本类属于WebMvcAutoConfiguration的内部类
   // 基类DelegatingWebMvcConfiguration直接继承自WebMvcConfigurationSupport, 也就是WebMvcConfigurationSupport为本类的父类
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
	@Bean
	@Override
	public Validator mvcValidator() {
		if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
			return super.mvcValidator();
		}
		//  Adapter底层的实例正是我们前文提到的LocalValidatorFactoryBean实例。
		return ValidatorAdapter.get(getApplicationContext(), getValidator());
	}

	@Override
	protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
		try {
			return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
		}
		catch (NoSuchBeanDefinitionException ex) {
		    // 此时上面获取不到ConfigurableWebBindingInitializer
		    //回调上面覆写的 mvcValidator() 方法,对validator进行了绑定
			return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
		}
	

	/**
	 * 所创建的ConfigurableWebBindingInitializer 实例, 其实现的接口方法initBinder()会在每次请求到来的时候都被调用, 为每个DataBinder进行初始化操作, 感兴趣的读者可以自行断点调试. 
	 * 上述的initBinder()方法中进行的一系列初始化操作就包括为WebDataBinder实例配置Validator实现
	 */
		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 范例将负责处理所有通过注解声明的Controller方法, 例如 @GetMapping / @PostMapping 等等. 而我们在这里正好将我们上面配置完毕的ConfigurableWebBindingInitializer实例注入到其内部,为每个请求进行参数校验提供保证。
	 */
	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
		RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
		adapter.setContentNegotiationManager(mvcContentNegotiationManager());
		adapter.setMessageConverters(getMessageConverters());		
           // 这个getConfigurableWebBindingInitializer()正是上面已经论述过的 		
        adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
        // 有自定义需要的时候可以考虑覆写如下方法getArgumentResolvers(), getReturnValueHandlers(), getMessageConverters()等.
		adapter.setCustomArgumentResolvers(getArgumentResolvers());
		adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
		return adapter;
	}		
}

因为,本例主要介绍controller请求中,requestBody中的参数校验,因此,在RequestMappingHandlerAdapter中我们主要关注对参数的解析。同理,RequestMappingHandlerAdapter继承了initialBean,所以,在实例化的过程中,会执行**afterPropertiesSet()**方法,如下所示:

	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if (this.argumentResolvers == null) {
		//该方法就是我们要找的请求参数解析器
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.initBinderArgumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

其中,我们重点关注getDefaultArgumentResolvers()方法中的new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice),而我们所期待的Validate逻辑正是发生在RequestResponseBodyMethodProcessor类的resolveArgument()方法中,该方法将在每次请求的时候被回调。

在这里插入图片描述

综上所述:
1.配置类 ValidationAutoConfiguration向Spring容器中注入的LocalValidatorFactoryBean借助 Spring自身提供一系列扩展性接口(诸如InitializingBean)等完成了对于 JSR303契约接口javax.validation.Validator (本例中实现者为hibernate validator中的ValidatorImpl)与 Spring自身的验证接口org.springframework.validation.Validator 的适配。
2.配置类WebMvcAutoConfiguration中通过配置RequestMappingHandlerAdapter(正是它负责处理Controller层注解@RequestMapping 修饰的方法请求调用)里的webBindingInitializer来介入到校验逻辑里来的。
本例要求解析入参注解@RequestBody的,正是在RequestMappingHandlerAdapter类中默认会进行注册的RequestResponseBodyMethodProcessor,而我们所期待的Validate逻辑正是发生在RequestResponseBodyMethodProcessor类的resolveArgument()方法中,该方法将在每次请求的时候被回调。

4.执行过程中校验

下面就到了,具体执行的时候,如何进行@RequestBody的校验了,既然我们确定到了RequestResponseBodyMethodProcessor类进行参数的解析,因此,通过断点找到resolveArgument()方法,如下图所示,可以猜到validateIfApplicable方法,就是我们一直要找的方法。
在这里插入图片描述
继续跟踪代码,可以发现最终调用的就是上面自动装配的Hibernate Validator中的ValidatorImpl类中的validate方法,如下图所示
在这里插入图片描述
如果校验失败,会抛出MethodArgumentNotValidException,如下图所示,此时项目中需要创建全局异常处理类,来接收该异常,抛出自己定义的异常,否则接口会抛出spring的 bad request异常,而看不到自己定义的异常信息。
在这里插入图片描述
以上就是针对controller中@RequestBody中接收参数校验的全流程,后续会陆续更新Service层参数校验和Configuration配置类等参数的校验原理,以及Validator的一些高级用法,谢谢。

举报

相关推荐

0 条评论