欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SSM 集成 hibernate validator中遇到的坑

程序员文章站 2022-06-20 10:42:39
...

初衷

对于Controller的请求参数,需要对其做各种校验,感觉太麻烦,整合hibernate来使用注解的方式进行参数校验,并自定义消息提示。

实现

实现思路:

  • 在Controller层使用Aop,对于验证出错的进行处理
  • 使用Controller的异常统一处理去做

第一个坑:我在SpringMvc的配置文件中配置了validator :org.springframework.validation.beanvalidation.LocalValidatorFactoryBean,
并将其属性validationMessageSource引用messageSource:org.springframework.context.support.ReloadableResourceBundleMessageSource。

然后我在Controller开启参数验证,通过Aop来切 @Pointcut(“execution(public * xxx.controller…*(…,org.springframework.validation.BindingResult,…))”),然后通过BindingResult中的内容进行处理。
然后我通过三种方式取i18n下的资源文件

  • 通过Message message
  • ReloadableResourceBundleMessageSource message
  • 通过上下文取(因为上下文实现了Message接口)
    但是,我获取的Message对象都是DelegatingMessageSource ,并且取不到对应资源文件,从上下文中也取不到。

SSM 集成 hibernate validator中遇到的坑

//public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean
//public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver 
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var4) {
                this.destroyBeans();
                this.cancelRefresh(var4);
                throw var4;
            }

        }
    }

protected void initMessageSource() {
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        if (beanFactory.containsLocalBean("messageSource")) {
            this.messageSource = (MessageSource)beanFactory.getBean("messageSource", MessageSource.class);
            if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
                HierarchicalMessageSource hms = (HierarchicalMessageSource)this.messageSource;
                if (hms.getParentMessageSource() == null) {
                    hms.setParentMessageSource(this.getInternalParentMessageSource());
                }
            }
        } else {
            DelegatingMessageSource dms = new DelegatingMessageSource();
            dms.setParentMessageSource(this.getInternalParentMessageSource());
            this.messageSource = dms;
            beanFactory.registerSingleton("messageSource", this.messageSource);
        }

    }

解决:
首先上图是Spring父子容器,在Spring启动的时候,首先加载父容器(Root WebApplication),然后在加载子容器。

然后看上述代码,在Spring的启动过程,this.initMessageSource();会给当前容器绑定Message对象——initMessageSource();

When an ApplicationContext is loaded, it automatically searches for a
MessageSource bean defined in the context. The bean must have the name
messageSource. If such a bean is found, all calls to the preceding
methods are delegated to the message source. If no message source is
found, the ApplicationContext attempts to find a parent containing a
bean with the same name. If it does, it uses that bean as the
MessageSource. If the ApplicationContext cannot find any source for
messages, an empty DelegatingMessageSource is instantiated in order to
be able to accept calls to the methods defined above.

initMessageSource:首先判断当前容器是否存在名message的bean,如果存在,则赋值到当前容器的message属性。如果不存在,则创建DelegatingMessageSource对象,DelegatingMessageSource通过引用父容器的Message对象,来提供服务,DelegatingMessageSource将赋值到当前容器的message属性;

第二个坑:@RequestBody 同@Validated一起使用,当验证失败时,400错误。

DispatcherServlet .doDispatch = > mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

//在其调用过程中,RequestResponseBodyMethodProcessor,对参数进行校验
 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Object arg = this.readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
        Annotation[] annotations = parameter.getParameterAnnotations();
        Annotation[] var10 = annotations;
        int var9 = annotations.length;

        for(int var8 = 0; var8 < var9; ++var8) {
            Annotation annot = var10[var8];
            if (annot.annotationType().getSimpleName().startsWith("Valid")) {
                String name = Conventions.getVariableNameForParameter(parameter);
                WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
                Object hints = AnnotationUtils.getValue(annot);
                binder.validate(hints instanceof Object[] ? (Object[])hints : new Object[]{hints});
                BindingResult bindingResult = binder.getBindingResult();
                if (bindingResult.hasErrors()) {
                    throw new MethodArgumentNotValidException(parameter, bindingResult);
                }
            }
        }

        return arg;
    }

如果在方法的参数注解上,标有以Valid注解开头的注解,将会去对参数进行校验,如果校验出错,则抛出throw new MethodArgumentNotValidException(parameter, bindingResult);。

当发生异常时调用改方法
mv = this.processHandlerException(processedRequest, response, handler, var21);

0 = {aaa@qq.com} 1 =
{aaa@qq.com} 2 =
{aaa@qq.com} 3 =
{aaa@qq.com} 4 =
{aaa@qq.com} 5 =
{aaa@qq.com} 6 = {aaa@qq.com}

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ModelAndView exMv = null;
        Iterator var7 = this.handlerExceptionResolvers.iterator();

        while(var7.hasNext()) {
            HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var7.next();
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }

        if (exMv != null) {
            if (exMv.isEmpty()) {
                return null;
            } else {
                if (!exMv.hasView()) {
                    exMv.setViewName(this.getDefaultViewName(request));
                }

                WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
                return exMv;
            }
        } else {
            throw ex;
        }
    }


//DefaultHandlerExceptionResolver
 public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (this.shouldApplyTo(request, handler)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
            }

            this.logException(ex, request);
            this.prepareResponse(ex, response);
            return this.doResolveException(request, response, handler, ex);
        } else {
            return null;
        }
    }

//DefaultHandlerExceptionResolver
 protected ModelAndView handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        response.sendError(400);
        return new ModelAndView();
    }

上边是对异常MethodArgumentNotValidException的处理,被DefaultHandlerExceptionResolver处理了

在提交Form表单的时候,能将验证的错误信息封装到BindingResult中,而json的请求格式则不行。百度搜索了半天,搜出的内容没有一点有用的信息。

从Spring 3.1开始,可以使用@Valid或@Validated注释对@RequestBody方法参数进行注释,以调用自动验证。
在这种情况下,Spring将自动执行验证,并在引发错误的情况下抛出MethodArgumentNotValidException。

解决方式:升级了Spring版本到4.0 。

相关标签: spring java