一、需求
今天在搭建Springboot框架的时候,又遇到一个需求:在多模块系统中,有些模块想自己管理BeanValidation的资源文件(默认是启动项目claspath下的 ValidationMessages.properties)。刚开始还天真地认为springboot会不会帮我们做了,结果并没有,于是就是撸源码了。
以下是我的实现和实现原理
二、实现
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
/**
* 当有异常时返回默认的验证器
* @return 返回的是org.springframework.validation.Validator,不是javax.validation.Validator
* 所以返回时要适配一下
*/
@Override
public Validator getValidator() {
//路径匹配
PathMatchingResourcePatternResolver resourcePatternResolver =
new PathMatchingResourcePatternResolver(
MyWebMvcConfigurer.class.getClassLoader());
try {
//匹配属性文件,有个限制,资源文件名称必须包含Validation
Resource[] resources =
resourcePatternResolver.getResources("classpath*:*Validation*.properties");
List<String> files = Arrays.stream(resources)
.filter(resource -> StringUtils.isNotBlank(resource.getFilename()))
.map(resource -> {
String fileName = resource.getFilename();
return fileName.substring(0, fileName.indexOf("."));
}).collect(Collectors.toList());
javax.validation.Validator validator = Validation.byDefaultProvider()
.configure()
//这里可以加载多个文件
.messageInterpolator(new ResourceBundleMessageInterpolator(
new AggregateResourceBundleLocator(files)))
.buildValidatorFactory()
.getValidator();
//适配
return new SpringValidatorAdapter(validator);
} catch (IOException e) {
//发生异常,返回null,springboot框架会采用默认的validator
return null;
}
}
三、实现原理
源码分析
1、定位Bean在什么地方验证的
DispatcherServlet验证Bean的主要源码路径
- RequestResponseBodyMethodProcessor#resolveArgument
- AbstractMessageConverterMethodArgumentResolver#validateIfApplicable
- DataBinder#validate(核心)
- AbstractMessageConverterMethodArgumentResolver#validateIfApplicable
源码:
/**
* 该方法定位:org.springframework.web.servlet.mvc.method.annotation.
RequestResponseBodyMethodProcessor#resolveArgument
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter,
parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
//*******校验方法参数是否符合要求*******
//调用链:也就是验证器被调用的地方
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()
&& isBindExceptionRequired(binder, parameter)) {
throw
new MethodArgumentNotValidException(parameter,
binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(
BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
/**
* 该方法定位:org.springframework.web.servlet.mvc.method.annotation.
AbstractMessageConverterMethodArgumentResolver#validateIfApplicable
*
* Validate the binding target if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param parameter the method parameter descriptor
* @since 4.1.5
* @see #isBindExceptionRequired
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null ||
ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ?
validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ?
(Object[]) hints : new Object[] {hints});
//调用链
binder.validate(validationHints);
break;
}
}
}
/**
* 该方法定位:org.springframework.validation.DataBinder#validate(java.lang.Object...)
*
* Invoke the specified Validators, if any, with the given validation hints.
* <p>Note: Validation hints may get ignored by the actual target Validator.
* @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
* @see #setValidator(Validator)
* @see SmartValidator#validate(Object, Errors, Object...)
* 核心方法
*/
public void validate(Object... validationHints) {
for (Validator validator : getValidators()) {
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(getTarget(),
getBindingResult(), validationHints);
}
else if (validator != null) {
validator.validate(getTarget(), getBindingResult());
}
}
}
/**
* 获取验证器(关键就在:this.validators怎么初始化的?)
*/
public List<Validator> getValidators() {
return Collections.unmodifiableList(this.validators);
}
发现:在DataBinder#validate中有验证Bean的核心代码validator.validate(...)
分析到这里关键就是validator在哪赋值的?
2、validators赋值
-
DataBinder属性validators赋值 private final List<Validator> validators = new ArrayList<>();
//断点跟踪发现: public void setValidator(@Nullable Validator validator) { assertValidators(validator); this.validators.clear(); if (validator != null) { this.validators.add(validator); } }
-
DataBinder#setValidator被调用的位置
- org.springframework.web.bind.support.ConfigurableWebBindingInitializer#initBinder
源码:
@Override public void initBinder(WebDataBinder binder) { binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths); if (this.directFieldAccess) { binder.initDirectFieldAccess(); } if (this.messageCodesResolver != null) { binder.setMessageCodesResolver(this.messageCodesResolver); } if (this.bindingErrorProcessor != null) { binder.setBindingErrorProcessor(this.bindingErrorProcessor); } if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) { //发现是在这里调用的,下面的问题就是ConfigurableWebBindingInitializer //中的validator属性在哪初始化的? //在对应的setValidator方法打断点 binder.setValidator(this.validator); } if (this.conversionService != null) { binder.setConversionService(this.conversionService); } if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { propertyEditorRegistrar.registerCustomEditors(binder); } } }
-
ConfigurableWebBindingInitializer#initBinder被调用的位置 研究发现:ConfigurableWebBindingInitializer#initBinder是在springboot初始化时被调用的 调用链如下: 调用链1:初始化springmvc的requestMappingHandlerAdapter EnableWebMvcConfiguration#requestMappingHandlerAdapter
- super.requestMappingHandlerAdapter();—>WebMvcConfigurationSupport 调用链2: WebMvcConfigurationSupport#requestMappingHandlerAdapter
- adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); 调用链3: EnableWebMvcConfiguration#ConfigurableWebBindingInitializer
- super.getConfigurableWebBindingInitializer(); 调用链4: WebMvcConfigurationSupport#ConfigurableWebBindingInitializer
- mvcValidator(),这个是核心
源码:
/** * @see org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration. * EnableWebMvcConfiguration#requestMappingHandlerAdapter */ @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { //调用链1:调用父类WebMvcConfigurationSupport#requestMappingHandlerAdapter RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; } /** * @seeorg.springframework.web.servlet.config.annotation. * WebMvcConfigurationSupport#requestMappingHandlerAdapter * */ public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(mvcContentNegotiationManager()); adapter.setMessageConverters(getMessageConverters()); //调用链2:EnableWebMvcConfiguration#getConfigurableWebBindingInitializer adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); ... } /** * @see springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration. * EnableWebMvcConfiguration#getConfigurableWebBindingInitializer * */ @Override protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { try { //这里是不存在实例,报异常 return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); } catch (NoSuchBeanDefinitionException ex) { //调用链3:WebMvcConfigurationSupport#getConfigurableWebBindingInitializer return super.getConfigurableWebBindingInitializer(); } } /** * @seespringframework.web.servlet.config.annotation.WebMvcConfigurationSupport * #getConfigurableWebBindingInitializer * */ protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); initializer.setConversionService(mvcConversionService()); //调用链4:核心方法mvcValidator() initializer.setValidator(mvcValidator()); MessageCodesResolver messageCodesResolver = getMessageCodesResolver(); if (messageCodesResolver != null) { initializer.setMessageCodesResolver(messageCodesResolver); } return initializer; }
-
3、validator是什么
通过源码分析,找到了关键点就是mvcValidator(),现在对其分析,找出其返回的validator到底是什么?
断点调试时发现mvcValidator()进入了
org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept
-
org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#resolveBeanReference resolveBeanReference方法里有个关键的代码
//关键在于 beanFactory.getBean(beanName),name = "mvcValidator",创建该实例 //从而会找到EnableWebMvcConfiguration的mvcValidator方法 //(因为mvcValidator方法上有@Bean,方法名称又与beanName相同,故调用) Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName));
-
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#mvcValidator 终于找到创建validator对象的点了,以下就是如何自己扩展? 继续研究创建validator的源码,寻找关键点
@Bean @Override public Validator mvcValidator() { if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { return super.mvcValidator(); } //关键在于getValidator()方法 //真正调用的是父类DelegatingWebMvcConfiguration#getValidator return ValidatorAdapter.get(getApplicationContext(), getValidator()); }
4、关键点:分析getValidator()方法
注意:这里就是我们可以扩展的地方
/**
* springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration#getValidator
*/
@Override
@Nullable
protected Validator getValidator() {
//configurers属性是WebMvcConfigurerComposite的对象
return this.configurers.getValidator();
}
/**
*@see springframework.web.servlet.config.annotation.WebMvcConfigurerComposite#getValidator
*/
@Override
public Validator getValidator() {
Validator selected = null;
//看到WebMvcConfigurer这个东西,我是很激动呀!终于看到曙光了,激动半天
//于是我就自定义了MyWebMvcConfigurer实现WebMvcConfigurer,并重写
//其中的getValidator方法,哈哈,终于找到扩展点了
for (WebMvcConfigurer configurer : this.delegates) {
Validator validator = configurer.getValidator();
if (validator != null) {
if (selected != null) {
throw new IllegalStateException("No unique Validator found: {" +
selected + ", " + validator + "}");
}
selected = validator;
}
}
return selected;
}
通过getValidator()获取自定义的validator后
ValidatorAdapter.get(getApplicationContext(), getValidator());对其包装如下:
/**
* @see springframework.boot.autoconfigure.validation.ValidatorAdapter#get
*/
public static Validator get(ApplicationContext applicationContext,
Validator validator) {
//如果为null(自定义的validator发生异常),返回默认的
if (validator != null) {
//因为非空,会执行该行代码
return wrap(validator, false);
}
return getExistingOrCreate(applicationContext);
}
private static Validator wrap(Validator validator, boolean existingBean) {
if (validator instanceof javax.validation.Validator) {
//执行该代码
if (validator instanceof SpringValidatorAdapter) {
return new ValidatorAdapter((SpringValidatorAdapter) validator,
existingBean);
}
return new ValidatorAdapter(
new SpringValidatorAdapter((javax.validation.Validator) validator),
existingBean);
}
return validator;
}
总结:在分析源码的过程中犯了最大的错误就是:总想什么都搞明白,跟踪每个源码的实现,结果发现还是没搞懂,白白浪费了很多时间。其实在分析源码的过程中,不需要钻牛角尖,把每个都搞懂。你要搞明白你的“关注点“在哪?,不要走着走着就走偏了。很多源码“观其大意”就行,没必要深究,不然就呵呵了。
来源:oschina
链接:https://my.oschina.net/u/4274582/blog/3301119