简介:结合JSR380,Hibernate validation,生成代理(也可以用Spring AOP来替代)实现的一整套的请求参数校验的框架,后期优化可以实现做成一个服务化的jar包到处使用。本文中将参数校验的所有代码都和业务代码逻辑进行分离,完美契合单一性原则、低耦合等设计原则。
全局启用注解:
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableValidation {
}
具体抽象的业务注解(若干,本文只举例一个):
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PaginationValidator.class)
@Documented
public @interface Pagination {
String message() default "{com.ctrip.ibu.common.annotation.coreAnnotation.Pagination.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
int minPageIndex() default 1;
int minPageSize() default 1;
int maxPageSize();
}
切入和解析代码
public class ValidationBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("##############################################postProcessBeforeInitialization###############################################");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("##############################################postProcessAfterInitialization###############################################");
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (AnnotationUtils.findAnnotation(targetClass, EnableValidation.class) != null) {
ProxyFactory proxyFactory = new ProxyFactory();
//proxyFactory.setTarget(bean);
proxyFactory.setTargetClass(targetClass);
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//ignore checkHealth()
if ("checkHealth".equals(invocation.getMethod().getName())) {
// do business logic.
Method method = invocation.getMethod();
return method.invoke(bean, invocation.getArguments());
// return invocation.proceed();
} else {
Object result = null;
try {
// Before: check request param
doPreHandle(invocation);
// do business logic.
Method method = invocation.getMethod();
result = method.invoke(bean, invocation.getArguments());
} finally {
// After: handle the result after finish business logic.
doAfterHandle(result);
}
return result;
}
}
});
return proxyFactory.getProxy( ClassUtils.getDefaultClassLoader());
}
return bean;
}
/**
* Before: check request param
* @param invocation
*/
private void doPreHandle(MethodInvocation invocation) throws Exception {
Object[] args = invocation.getArguments();
if (args == null || args.length < 1) {
throw new Exception("request param size should >=1");
}
Object testRequest = args[0];
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Set<ConstraintViolation<Object>> constraintViolationSet = supportMessagePropertity(factory).getValidator().validate(testRequest);
// Encapsulate the condition that the parameter check is not passed
if (!CollectionUtils.isEmpty(constraintViolationSet)) {
List errorList = new ArrayList(constraintViolationSet.size());
for (Iterator<ConstraintViolation<Object>> constraintViolation = constraintViolationSet.iterator(); constraintViolation.hasNext();) {
ConstraintViolation error = constraintViolation.next();
Object errorParam = error.getInvalidValue();
String errorMessage = error.getMessage();
errorList.add("[" + String.valueOf(errorParam) + "] error! Error reason: " + errorMessage);
}
throw new IllegalRequestParamException(errorList.toString());
}
//原生的对Hibernate validation的自己的实现,有兴趣的可以试一下
// Field[] declaredFields = testRequest.getClass().getDeclaredFields();
for (Field field : declaredFields) {
Locale locale =
AnnotationUtils.findAnnotation(field, Locale.class);
if (locale != null) {
Constraint constraint =
AnnotationUtils.findAnnotation(Locale.class, Constraint.class);
Class<? extends ConstraintValidator<?, ?>>[] validatorClasses = constraint.validatedBy();
for (Class<? extends ConstraintValidator<?, ?>> validatorClass : validatorClasses) {
ConstraintValidator validator = BeanUtils.instantiate(validatorClass);
validator.isValid(testRequest, null);
}
}
// }
}
private HibernateValidatorContext supportMessagePropertity(ValidatorFactory factory){
return factory.unwrap(HibernateValidatorFactory.class)
.usingContext().messageInterpolator(new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator("validation-request-params")
)).failFast(true);
}
/**
* After: handle the result after finish business logic.
* @param result
*/
private void doAfterHandle(Object result) {
// do nothing.
}
}
若是Spring MVC 方式的请求参数的校验,可以如下配置:
/**
* Configuration for validation
*/
@Configuration
public class ValidationConfiguration extends WebMvcConfigurerAdapter {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:validation-request-params");
return messageSource;
}
@Bean
public LocalValidatorFactoryBean validatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
Map<String, String> propertyMap = new HashMap<>(4);
// fail fast mode
propertyMap.put(HibernateValidatorConfiguration.FAIL_FAST, "false");
bean.setValidationPropertyMap(propertyMap);
bean.setValidationMessageSource(messageSource());
return bean;
}
@Override
public Validator getValidator() {
return validatorFactoryBean();
}
}
private HibernateValidatorContext supportMessagePropertity(ValidatorFactory factory){
return factory.unwrap(HibernateValidatorFactory.class)
.usingContext().messageInterpolator(new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator("validation-request-params")
)).failFast(true);
}
否则可以使用ValidationBeanPostProcessor类中的supportMessagePropertity的方法来支持。
异常类比较简单:
public class IllegalRequestParamException extends RuntimeException {
public IllegalRequestParamException() {
}
public IllegalRequestParamException(String message) {
super(message);
}
public IllegalRequestParamException(String message, Throwable cause) {
super(message, cause);
}
public IllegalRequestParamException(Throwable cause) {
super(cause);
}
}
支持的自定义的参数校验后的错误提示,更友好,注:可以通过解析的代码中传入locale来实现国际化,即不同语种展示的错误信息自动的转化为对应语言的错误信息:
validation-request-params_zh.properties文件:(value也可以写中文,支持多语言)
com.ctrip.ibu.common.annotation.coreAnnotation.Id.message = Id role : required must be {required}, min must greater than {min}, max must less than {max}
com.ctrip.ibu.common.annotation.coreAnnotation.Locale.message = Locale role : required must be {required}, length must be 5
com.ctrip.ibu.common.annotation.coreAnnotation.Pagination.message = Pagination role : pageSize and pageIndex() can not be null, pageIndex must greater than {minPageIndex}, pageSize must between [{minPageSize}, {maxPageSize}