SpringBoot 集成Hibernate Validator RestController找不到的问题404

工程是用Springboot实现, 想要实现请求中的实体类的基本校验,用的是hibernate的 Validator, 用Swagger2构建RestAPI文档 问题是这样的,

有个controller是个接口:

public interface UserController {

    @PostMapping("/login")

    Result login(User user, BindingResult result);

}

一个实现类实现了它

@RestController

@Api(value = "测试登陆接口")

public class UserControllerImpl implements UserController{

    @ApiOperation(value = "登陆")

    @PostMapping("/login")

    public Result login(@RequestBody @Valid User user, BindingResult result) {

        return new Result("0", "登陆成功!");

    }

}

Entity就不写了,启动工程之后, 打开 http://localhost:7070/swagger-ui.html/ 显示成了这样式儿的

image

我晕,上网查了好多,根本没有跟我这个问题相关的,于是呢,我就把“implements UserController"这句删了,不让它实现接口,然后把@PostMapping注解挪到实现类里面,重启,就好用了。。。好用了。。用了。了。。。

这是为啥呢,我不甘心,又尝试了一下,回退成有问题那样,然后把方法里的BindingResult参数给删了,里面用到result对象的代码也都注释掉了,不让它报错,再重启, 又好用了。。。好用了。。用了。了。。。

我去,难道是controller实现接口的话方法声明中不能有接口类型的参数吗(BindingResult是个接口), 于是我又自己定义了一个接口IResult, 把它当做参数传给login方法,结果呢,呵呵,果然不好用

我又把这个参数改成了类类型的比如String,这种就好用

难道我要是想用BindingResult的话必须controller不能实现接口吗??这俩有几毛钱的关系啊?

这时我的内心是这样的

image

哪位大神给我解答一下,不胜感激。。。

-----------------------我是一条华丽丽的分割线----------------------------------

问题已经解决了,通过研究了一下spring的核心代码,现在总结一下,希望能帮到跟我一样遇到这种问题的童鞋

上面的代码其实没有描述完整,其实我加的@Valid注解细心的童鞋应该看出来我是想校验这个入参,但是因为公司的controller接口方法是在太多了,

每一个方法里面都加上if(result.hasErrors()){...}会疯掉的,代码也不好看,于是我就想加个切面,

切面里的方法是这样式儿的

@Around("execution(* com.caroline.controller.*.*(..)) && args(..,bindingResult)")

public ErrorResult doAround(ProceedingJoinPoint pjp, BindingResult bindingResult) throws Throwable {

    ErrorResult retVal = new ErrorResult();

    if (bindingResult.hasErrors()) {

        retVal = doErrorHandle(bindingResult);

    } else {

        retVal = pjp.proceed();

    }

    return retVal;

}

在这里面统一处理BindingResult. 但是启动的时候spring根本没有注入我的controller,大家可以通过log更直观的看到

image

我用的beyond Compare对比了两次的启动log发现了这个猫腻, 人家明明白白告诉你了,我没有注入你这个login的mapping

于是我就想知道为啥,我到底哪错了,你告诉我,我改还不行么。。。

要想知道为啥,就得看源码了,我在log里面发现了这个类AbstractHandlerMethodMapping, 在这个类的initHandlerMethods方法上加了断点,大家可以看源码截图

image

为什么要在这加断点?因为我debug时发现只有在获取UserControllerImpl的时候得到的beanType是这个奇怪的东东(spring启动时其他所有bean都能正确加载,即获取到正确的beanType)

com.sun.proxy.$Proxy71

这是啥?调查发现,会返回这个东西,是因为Spring通过注解发现了我这个controller它不是一般的controller,里面用了@Valid注解需要校验,还用了切面,要去切面的注解里面查一些约束的东西

org.springframework.aop.framework.ProxyFactory: 1 interfaces [com.caroline.controller.UserController];

2 advisors [org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR,

InstantiationModelAwarePointcutAdvisor:

expression [execution(* com.caroline.controller.*.*(..)) && args(..,bindingResult)];

advice method [public com.caroline.Entity.ErrorResult

com.caroline.interceptor.ControllerValidatorInterceptor.doAround(org.aspectj.lang.ProceedingJoinPoint,org.springframework.validation.BindingResult)

throws java.lang.Throwable]; perClauseKind=SINGLETON];

targetSource [SingletonTargetSource for target object [com.caroline.controller.UserControllerImpl@31a2a9fa]];

proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false

如果你有耐心看完应该就知道了,因为这里面的约束起了作用,get不到正确的beanType, 所以返回了上面那个奇怪的东东。

其实原因在这句

expression [execution(* com.caroline.controller.*.*(..)) && args(..,bindingResult)];

这个切面表达式写的有问题,上网查了一下,像我上面那种写法只能扫到controller的子包及其子包的类,是扫不到controller下面的类的,而我的类呢?

image
image

悲剧,所以spring就没办法注入这个userControllerImpl的bean了。。。 解决方法就是改成这样

expression [execution(* com.caroline.*.*(..)) && args(..,bindingResult)];

完美解决!!!

image

码了这么多字是给我自己做个笔记,也希望帮助到大家

PS: 最近总是遇到包名的问题还有bean name格式的问题

比如

public class EntityA{

    private EntityB entityB;//不要写成eb这样,最好是类型copy一下然后首字母小写

}

可能例子举得不太好,但是意思应该明白,如果是因为命名引起的问题调查半天是很崩溃的,我自己就遇到过,太低级了

下回写一篇我在用Hibernate Validator的遇到的问题吧,先立个flag。

-------------------------------------我还是那条华丽的分割线--------------------------------------------------------------

今天续更,我本来已经解决了这个问题,然后我们组另外一个开发用了另一种方式实现了目的,说是我的方法太繁琐。。。咳咳好吧,在每个controller方法里面加一个BindingResult参数确实繁琐。 下面我说一下这种方式具体的实现。

如果controller里面没有BindingResult这个参数,那么如果校验失败,会抛出一个这样的异常,叫这个

org.springframework.web.bind.MethodArgumentNotValidException

这个post request 的返回值差不多是这样

{

  "timestamp": 1523589643865,

  "status": 400,

  "error": "Bad Request",

  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",

  "errors": [

    {

      "codes": [

        "NotBlank.user.childUser.childUsername",

        "NotBlank.childUser.childUsername",

        "NotBlank.childUsername",

        "NotBlank.java.lang.String",

        "NotBlank"

      ],

      "arguments": [

        {

           "codes": [

           "user.childUser.childUsername",

           "childUser.childUsername"

            ],

           "arguments": null,

          "defaultMessage": "childUser.childUsername",

          "code": "childUser.childUsername"

        }

      ],

      "defaultMessage": "子用户名不能为空",

    "objectName": "user",

    "field": "childUser.childUsername",

    "rejectedValue": "",

    "bindingFailure": false,

    "code": "NotBlank"

  },

  {

    "codes": [

      "NotBlank.user.password",

      "NotBlank.password",

      "NotBlank.java.lang.String",

      "NotBlank"

    ],

    "arguments": [

      {

        "codes": [

          "user.password",

          "password"

        ],

        "arguments": null,

        "defaultMessage": "password",

        "code": "password"

      }

    ],

    "defaultMessage": "用户名不能为空",

    "objectName": "user",

    "field": "password",

    "rejectedValue": "",

    "bindingFailure": false,

    "code": "NotBlank"

  }

],

"message": "Validation failed for object='user'. Error count: 2",

"path": "/login"

}

其实已经有了error message了,我们想要的就是需要定义一个全局处理异常的切面类,在里面加工一下这个exception,返回一个我们需要的json串

@RestControllerAdvice

public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)

    public Result beanValidation(Exception exception){

        if (exception instanceof MethodArgumentNotValidException) {

           MethodArgumentNotValidException e = (MethodArgumentNotValidException)exception;

            User req = (User) e.getBindingResult().getTarget();

            final List errors = e.getBindingResult().getFieldErrors().stream()

                    .map(DefaultMessageSourceResolvable::getDefaultMessage)

                    .collect(toList());

            return new Result("-1", errors.toString());

        }

        return new Result("-99", "未知异常");

    }

}

这样就可以了,重启之后发送同样的消息,得到如下结果

{

  "code": "-1",

  "message": "[用户名不能为空, 子用户名不能为空]"

}

之前我们的ControllerImpl就可以省去BindingResult参数啦

image

这个是截图,因为代码显示不出消除线,想copy代码的可以看上面喔。

虽然我很不服气,但是不得不承认他这种方式对代码的侵入性更小,我要向他学习~!

以上代码可以在github中下载

移步github走起:https://github.com/CarolineHuang5954/Validator.git

如有问题欢迎讨论!~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容