因为上一篇博文讲到了Bean Validation在SpringBoot中的使用,为了让你知其然而知其所以然,我决定写一篇相关的源码解读,在这里让你完全理解Bean Validation的使用原理。
在了解这篇文章前,你需要先知道,Spring是如何将get请求中url里面的入参映射到类中的,是通过HandlerMethodArgumentResolver类的resolveArgument方法。在Spring中默认执行接口HandlerMethodArgumentResolver类的resolveArgument方法的实现类是ModelAttributeMethodProcessor类。
一、渲染入参,进行校验
下面的红框中就是在代码渲染完成后进行了校验,这个方法是在resolveArgument方法中
最后通过一系列的方法调用,走到了hibernate-validator包中的ValidatorImpl类的validate方法中,到这一步,方法调用的时序图如下:
此时,你肯定有疑问了,怎么从Spring的Validator最后调用到了hibernate-validator包中的Validator中的呢?
二、从Spring到hibernate-validator
1、入口
这一切都要从spring-context中的一个类说起,即LocalValidatorFactoryBean,这个类就是负责加载外部实现的验证类到Spring中,它有一个方法afterPropertiesSet。
这个方法属性Spring的都不会陌生,这个方法是Spring的主流程执行完会执行的方法。这个流程会走到2处,即:
configuration = bootstrap.configure();
2、加载外部实现类
最后会进入Validation类的run方法中:
上图中,真正去加载验证类的,是这行代码:
List<ValidationProvider<?>> cachedContextClassLoaderProviderList = getCachedValidationProviders( classloader );
这行代码中,使用了一个参数classLoader,是通过
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
关于ContextClassLoader
获取的,可以看到这是一个ContextClassLoader,这里关于类加载器涉及到双亲委派模型的只能从下级ClassLoader去向上级ClassLoader的问题,ContextClassLoader就解决了,怎么让上层的ClassLoader加载的类,使用下级的类加载的ClassLoader,于是便将ContextClassLoader放到了当前线程中,在这里你只需要知道ContextClassLoader是为了能让Spring加载下级的jar包中类即可。
加载Hibernate中的类
真正加载Hebernate-validator包的代码调用是这一段方法:
private synchronized List<ValidationProvider<?>> getCachedValidationProviders(ClassLoader classLoader) {
SoftReference<List<ValidationProvider<?>>> ref = providersPerClassloader.get( classLoader );
return ref != null ? ref.get() : null;
}
可以看到这样代码会加载jar包中,ValidationProvider实现类,而hibernate-validator包中的HibernateValidator就实现了ValidationProvider接口,这样类就被加载进来了。
后面的流程就是根据HibernateValidator去实现相应的方法了。关于Bean Validation的各种工具流程,这里有一篇比较全面的文章.,结合我的源码讲解去看,你会了解的更加全面。
三、hibernate-validator中的流程
从Spring到hibernate-validator的流程就说完了,后面的执行就在hibernate-validator包中了,此后的逻辑我大致概括下:
- 根据group分组去验证,如果没有group分组就默认验证,
- 到指定的Validator验证类中去验证
- 如果验证失败返回错误信息
我们看下源码:
验证当前分组:
最后走到了NotNullValidator中的isValid方法,
这其中的时序图如下:
四、各种设计原则
到这里流程算是告一段落,但是我们的分析还没完,在整个流程中涉及到了多种设计原则,搞懂流程不算源码,搞懂其中牵涉到的各种设计原则和模式才算让这段源码了然于胸。
- 单一职责原则:在这些Validator中,一个NotNull就是一个Validator,各种验证互相独立,层次分明
- 接口隔离原则:所有Validator都是实现ConstraintValidator接口,易于扩展
- 开闭原则:Spring通过ContextClassLoader去加载验证的模块,当新增验证模块的时候不需要修改Spring代码,只需要增加验证模块即可
- 迪米特法则:ModelAttributeMethodProcessor类实现了HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler,分别处理数据的渲染和参数的返回。
设计模式: - 策略模式:通过条件筛选对应的Validator,当新增验证方式的时候,只需要新增Validator即可。
- 单例模式:每个Validator只创建一个,缓存在内存的Map中,需要使用的时候直接获取。