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

访问RestController报404错误

程序员文章站 2022-05-29 19:38:01
...

工程是用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<ChildUser> user, BindingResult result) {
        return new Result("0", "登陆成功!");
    }
}

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

访问RestController报404错误

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

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

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

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

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

这时我的内心是这样的

访问RestController报404错误

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

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

问题已经解决了,通过研究了一下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更直观的看到

访问RestController报404错误

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

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

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

访问RestController报404错误

为什么要在这加断点?因为我debug时发现只有在获取UserControllerImpl的时候得到的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 [aaa@qq.com]];
proxyTargetClass=false; optimize=false; opaque=false; exposeProxy=false; frozen=false

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

其实原因我已经标出来了,是那个切面表达式写的有问题,上网查了一下,像我上面那种写法只能扫到controller的子包及其子包的类,是扫不到controller下面的类的,而我的类呢?访问RestController报404错误

访问RestController报404错误

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

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

完美解决!!!

访问RestController报404错误

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

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<String> 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参数啦

访问RestController报404错误

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

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

以上代码可以在github中下载

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

如有问题欢迎讨论!~~