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的一些高级用法,谢谢。