引言
对于Java程序员来说,注解@NotBlank应该再熟悉不过了,其用于判断字符串是否为空”, 这个注解的使用需要通过maven引入hibernate相关jar包
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.8.Final</version>
</dependency>
打开@NotBlank注解源码可以得知,其可以加在方法、属性、构造器...上面,当传入的字符串为空时,@NotBlank注解就能提示我们配置好的message信息。@NotBlank注解的工作原理是啥,为啥短短一行代码,就能实现我们需要的功能,接下来本文将深入@NotBlank注解的底层,了解其是如何工作的。
@NotBalank注解和解析源码
Java程序员都知道实现自定义的注解,我们需要做两件事。第一件事,定义注解,第二件事,解析注解。以@NotBlank注解为例,第一件事,我们定义了一个名为@NotBlank的注解,如下源码:
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@code @NotBlank} constraints on the same element.
*
* @see NotBlank
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
接下来第二件事,我们需要解析该注解,一般来说,我们需要实现ConstraintValidator接口,@NotBlank的解析类为NotBlankValidator,其实现了ConstraintValidator接口,如下源码:
public class NotBlankValidator implements ConstraintValidator<NotBlank, CharSequence> {
/**
* Checks that the character sequence is not {@code null} nor empty after removing any leading or trailing
* whitespace.
*
* @param charSequence the character sequence to validate
* @param constraintValidatorContext context in which the constraint is evaluated
* @return returns {@code true} if the string is not {@code null} and the length of the trimmed
* {@code charSequence} is strictly superior to 0, {@code false} otherwise
*/
@Override
public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
if ( charSequence == null ) {
return false;
}
return charSequence.toString().trim().length() > 0;
}
}
代码调试
我们将断点打在NotBlankValidator类的某行代码上,然后发送下图所示的请求,
这样我们就可以清楚地知道代码执行逻辑。红色箭头表示为重要的核心方法,蓝色箭头表示调用过程。
发起的Http请求会被拦截,从而会执行到
RequestResponseBodyMethodProcessor类中的【resolveArgument】方法,最终会执行到NotBlankValidator类的【isValid】方法。相信读到这里大家已经知道了@NotBlank注解的原理。
读到这里,相信读者有很多疑问,例如:
- 疑问一:
RequestResponseBodyMethodProcessor类是如何加载到容器中的? - 疑问二:程序为啥会选择
NotBlankValidator类去解析@NotBlank注解,而不是选择其他的解析器?
首先来解决下疑问二,在SimpleConstraintTree类中,如下所示代码行获取到@NotBlank的注解解析器NotBlankValidator
ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
【getInitializedConstraintValidator】方法使用双重检查锁机制来获取注解解析器,如果缓存constraintValidatorCache中存在这个注解解析器,则直接返回,其中缓存key为:,否则最终会调用如下方法获取
private <A extends Annotation> ConstraintValidatorDescriptor<A> findMatchingValidatorDescriptor(ConstraintDescriptorImpl<A> descriptor, Type validatedValueType) {
Map<Type, ConstraintValidatorDescriptor<A>> availableValidatorDescriptors = TypeHelper.getValidatorTypes(
descriptor.getAnnotationType(),
descriptor.getMatchingConstraintValidatorDescriptors()
);
List<Type> discoveredSuitableTypes = findSuitableValidatorTypes( validatedValueType, availableValidatorDescriptors.keySet() );
resolveAssignableTypes( discoveredSuitableTypes );
if ( discoveredSuitableTypes.size() == 0 ) {
return null;
}
if ( discoveredSuitableTypes.size() > 1 ) {
throw LOG.getMoreThanOneValidatorFoundForTypeException( validatedValueType, discoveredSuitableTypes );
}
Type suitableType = discoveredSuitableTypes.get( 0 );
return availableValidatorDescriptors.get( suitableType );
}
。
接下来,我将阐述RequestResponseBodyMethodProcessor类是如何被加载到容器中,以及程序为啥会选择NotBlankValidator解析类去解析,
附录
在SpringMvc中,RequestResponseBodyMethodProcessor类会对http请求中的RequestBody和ResponseBody后处理。该类解析传入的参数核心方法截图如下: