让Spring Controller 的方法基本数据类型参数支持Bean Validation

让Spring Controller 的方法基本数据类型参数支持Bean Validation

   

Spring中的Bean Validation

    咱们知道Spring MVC层是默承认以支持Bean Validation的,尝试使用了一下感受很不方便,只支持对Bean的验证,还须要在Bean后面加一个BindingResult做为参数。而在咱们的工程中有不少接口都是基本数据类型做为参数的。下面分享怎么来实现MVC层对基本数据类型提供Bean Validation的支持。前端

  Spring Mvc 层默认的Bean Validation支持配置能够参考这位朋友的博文:http://jinnianshilongnian.iteye.com/blog/1990081git

    Spring中对Bean Validation能够支持方法级别的验证的,这块只支持Service层的,能够参考http://jinnianshilongnian.iteye.com/blog/1495594 。咱们的实现中的不少代码也是参考了Spring这块的实现。web

MVC层Bean Validation的扩展

    咱们经过mvc的拦截器配合注解来实现这样的一个扩展。咱们拦截Controller方法上带有@Valid注解的方法(这个注解来自Bean Validation规范),而后经过Spring的RequestMappingHandlerAdapter来获取每个参数的值,而后使用org.hibernate.validator.internal.engine.ValidatorImpl (Hibernate对Bean Validation规范的实现,它额外提供了对方法参数的验证,本来的规范中的api是没有的)来根据每一个参数上面的验证注解来验证这些参数是否合法。验证失败的话抛出ConstraintViolationException。ajax

    这样的一个验证过程,下面是这个拦截器的代码:spring

 

/**
 * 添加Spring Controller 方法级别的Bean Validation的支持
 * @author JiangFeng
 *
 */
public class ValidationInterceptor implements HandlerInterceptor{
	protected final Log logger = LogFactory.getLog(getClass());
	
	private List<HandlerMethodArgumentResolver> argumentResolvers;
	@Autowired
	private Validator validator;
	@Autowired
	private RequestMappingHandlerAdapter adapter;
	
	private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
			new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
	private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<Class<?>, Set<Method>>(64);

	@Autowired
	public ValidationInterceptor(RequestMappingHandlerAdapter requestMappingHandlerAdapter){
		argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
	}

	@Override
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
		
		LocalValidatorFactoryBean validatorFactoryBean = (LocalValidatorFactoryBean)validator;
		ValidatorImpl validatorImpl = (ValidatorImpl) validatorFactoryBean.getValidator();
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		HandlerMethod method = (HandlerMethod)handler;
		Valid valid = method.getMethodAnnotation(Valid.class);
		if(valid!=null){
			Class<?>[] groups = new Class<?>[0];
			MethodParameter[] parameters = method.getMethodParameters();
			Object[] parameterValues = new Object[parameters.length];
			for (int i = 0; i < parameters.length; i++) {
				MethodParameter parameter = parameters[i];
				HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
				Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
				ModelAndViewContainer mavContainer = new ModelAndViewContainer();
				mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
				WebDataBinderFactory webDataBinderFactory = getDataBinderFactory(method);
				Object value = resolver.resolveArgument(parameter, mavContainer, webRequest, webDataBinderFactory);
				parameterValues[i] = value;
			}
			Set<ConstraintViolation<Object>> violations = validatorImpl.validateParameters(method.getBean(), method.getMethod(), parameterValues, groups);
			if (!violations.isEmpty()) {
				throw new ConstraintViolationException(violations);
			}
		}
		return true;
	}
	
	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		Class<?> handlerType = handlerMethod.getBeanType();
		Set<Method> methods = this.initBinderCache.get(handlerType);
		if (methods == null) {
			methods = HandlerMethodSelector.selectMethods(handlerType, RequestMappingHandlerAdapter.INIT_BINDER_METHODS);
			this.initBinderCache.put(handlerType, methods);
		}
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(new InvocableHandlerMethod(bean, method));
		}
		return new ServletRequestDataBinderFactory(initBinderMethods, adapter.getWebBindingInitializer());
	}

	private HandlerMethodArgumentResolver getArgumentResolver(
			MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
				if (logger.isTraceEnabled()) {
					logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
							parameter.getGenericParameterType() + "]");
				}
				if (methodArgumentResolver.supportsParameter(parameter)) {
					result = methodArgumentResolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

	@Override
	public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		
	}

	@Override
	public void afterCompletion(HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		
	}

}

咱们还须要在spring mvc的配置文件中启用Bean Validationjson

 

<bean id="validator"
		class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
		<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
		<!-- 若是不加默认到 使用classpath下的 ValidationMessages.properties -->
		<property name="validationMessageSource" ref="messageSource" />
	</bean>

	<!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->
	<bean id="messageSource"
		class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="useCodeAsDefaultMessage" value="false" />
		<property name="defaultEncoding" value="UTF-8" />
		<property name="cacheSeconds" value="60" />
	</bean>

 

具体应用

    Bean Validation配合上统一异常处理,而后再加上前端js里面对json的统一解析。整个开发过程当中已经不须要对验证作任何处理了。api

    个人框架中的应用场景的代码mvc

@RequestMapping("/handle/addFacilityAdd.action")
    @ResponseBody
    @Valid
    @Workflow
    public Result addFacilityAdd(
    		 @NotEmpty(message = "设备类型不能为空") @RequestParam(value = "facilityId", required = false) String facilityId,
             @Digits(fraction = 6, integer = 3) @RequestParam(value = "longitude", required = false) Double longitude,
             @Digits(fraction = 6, integer = 3) @RequestParam(value = "latitude", required = false) Double latitude,
             @NotEmpty(message = "地址不能为空") @RequestParam(value = "address", required = false) String address,
             @NotEmpty(message = "内容不能为空") @RequestParam(value = "content", required = false) String content)
            throws Exception

演示失败返回app

{"success":false,"returnTime":"2014-04-18 10:51:50","error":{"errorCode":1002,"errorMessage":"错误的参数","fieldErrors":[{"field":"content","message":"内容不能为空"},{"field":"address","message":"地址不能为空"},{"field":"facilityId","message":"设备类型不能为空"}]},"result":{}}

统一异常拦截后的前端验证js方法框架

var Valid = {};
Valid.valid = function (d) {
    if(typeof d !='object'){
        d = eval('(' + d + ')');
    }
    if (!d.success) {
        if(d.error.fieldErrors.length>0){
            for (var error in d.error.fieldErrors) {
                Alert.warning(d.error.fieldErrors[error].message);
            }
        }else{
            Alert.warning(d.error.errorMessage);
        }

        try {
            console.error(d);
        } catch (e) {

        }
        return false;
    } else {
        return true;
    }
}

在ajax的回调方法中先调用一验证,就能够直接提示出Bean Validation验证失败的错误信息了。

    

拦截器代码  http://git.oschina.net/for-1988/gbh91frxjy6msalqp28vi.code.git

相关文章
相关标签/搜索