Spring MVC中带有BindingResult的接口报错NoSuchMethodException的问题

发表于2020-05-28,长度3014, 390个单词, 8分钟读完
Flag Counter

现在开发Java web项目基本都是Spring框架的天下。而对接前端也基本使用Spring MVC,包括spring cloud。前端接口进行参数校验时,一种常用的方法是在参数里面增加@Valid注解,然后在参数签名中传入BindingResult result紧邻待校验参数来获取异常校验信息。

虽然@Valid和@Validation似乎功能一样,但我遇见的问题时,起码@Validation没有像@Valid一样能够嵌套校验。

今天遇到一个问题刚开始让人很懵逼。我需要通过aop拦截mvc的方法(过去一般叫controller中的action),然后通过反射拿到当前的方法引用,以判断方法上面是否有A或B或C或D注解,如果有则拿到注解的引用。

通过反射获取方法很简单,大家使用反射的话都是一样的:

public void filter(JoinPoint point) {
        Object[] args = point.getArgs();
        Class<?>[] argTypes = new Class[point.getArgs().length];
        for (int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }
        Method method;
        try { 
            // 这里通过反射拿到方法引用
            method = point.getTarget().getClass().getMethod(point.getSignature().getName(), argTypes);
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException(e);
        }
       if (method != null) {
           A bodyIndicator = method.getAnnotation(A.class);
           B bodyIndicator = method.getAnnotation(B.class);
           C bodyIndicator = method.getAnnotation(C.class);
           D bodyIndicator = method.getAnnotation(D.class);
            // 其他逻辑
       }
}

流程就是先通过aop的参数值获取其类型集合,然后调用java.lang.Class.getMethod(String name, Class<?>… parameterTypes)拿到方法。然后很快测试人员告诉我后台报错:

java.lang.NoSuchMethodException: XXXServiceImpl.update(XXXVO, org.springframework.validation.BeanPropertyBindingResult)
        at java.lang.Class.getMethod(Class.java:1786)
        at XXXXAspect.filter(XXXXAspect.java:87)
        at sun.reflect.GeneratedMethodAccessor193.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:611)
        at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:43)
        at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:51)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
        at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:97)

也就是说aop找不到正在执行的方法。方开始一眼望去很是纳闷:怎么会没有呢,怎么可能没有呢?!

于是在getMehod那里加了断点,一步步走进去,发现在下面的地方竟然返回了false:

既然是false,就看一下两个参数类型分别是啥。我的方法定义是

void update(@RequestBody XXXVO vo, BindingResult result);

而看到报错信息中的类型并非BindingResult,而是BeanPropertyBindingResult。

到这里原因就清晰了:BindingResult是一个接口,在实际传参的时候传入的是其实现类。所以修改代码如下:

for (int i = 0; i < args.length; i++) {
    argTypes[i] = args[i].getClass();
    if (args[i] instanceof BindingResult) {
        // BindingResult的需要转换类型
        argTypes[i] = BindingResult.class;
    }
}

那么最后思考一个问题:如果一个方法接收的是Object类型参数,那么传进去的值类型是什么都有可能,怎么匹配到这个方法呢?

Written on May 28, 2020
分类: dev, 标签: java
如果你喜欢,请赞赏! davelet