V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
anzu
V2EX  ›  Java

Spring Boot 一小坑浪费一下午,虽然解决了但还是不知道原因

  •  
  •   anzu · Mar 28, 2019 · 5870 views
    This topic created in 2591 days ago, the information mentioned may be changed or developed.

    代码并不复杂,写完 Controller 的一点基本代码后,想给传入参数做个验证,于是用了框架提供的 validation,然后噩梦开始了!

    要给 @ RequestParam 的参数做验证,则需要在 controller 上注解 @ Validated,而一旦加上此注解,controller 内用 @ Autowired 注解的 service 则无法被注入,其值为 null。

    // 如果删除下面这行,则 Autowired 正常
    @Validated
    @RestController
    @RequestMapping("/test")
    public class UserController {
        @Autowired
        private UserService userService;
    
        @GetMapping("/hello")
        private String sayHello(
            @RequestParam @Length(min = 5) String username,
            @RequestParam @Range(min = 1, max = 100) Integer age
        ) {
            // 如果保留 Validated,则 userService == null,下面这行报错
            return userService.getHello(username, age);
        }
    }
    

    这种诡异的现象一度让我怀疑 @ Validated 与 @ Autowired 有冲突,然而搜索了很多网页都未找到有人遇到类似的问题。

    更让人崩溃的是这些注解仿佛魔法一般,所有教程都告诉你,只要加上这个小东西就能轻松方便地完成功能了哦,但具体是什么原理一两句话说不清楚。

    于是代码很难调试,不如说我根本无从下手,调试第一步——断点该设置在哪里呢?毫无头绪。

    折腾过程略,我直接说解决方法吧:删除方法的 private 修饰,只要方法不为 private 即可。为什么?我也不太清楚,猜测可能 @ Validated 用了 Spring AOP,导致 private 方法无法被代理。希望知道确切原因的好心人能讲解一下,谢谢。

    26 replies    2019-04-02 10:23:00 +08:00
    lufer
        1
    lufer  
       Mar 28, 2019
    valid 注解为什么不加在参数前边,另外接口多参数的话你写个 vm 统一校验不会优雅很多吗
    MoHen9
        2
    MoHen9  
       Mar 28, 2019 via Android
    Validated 是加在 VO 和接口方法上的,你加在 controller 是不对的。
    MoHen9
        3
    MoHen9  
       Mar 28, 2019 via Android
    建议点进去看注释说明
    gaius
        4
    gaius  
       Mar 28, 2019
    你在哪看的加到 controller 上
    jinue9900
        5
    jinue9900  
       Mar 28, 2019
    参数校验用的是 @Valid
    mushishi
        6
    mushishi  
       Mar 28, 2019
    我一般是用在 BO 里面,然后 controller 里面接收 Bo 做基本的参数校验,隐约记得是不能放在 private 方法上的,没研究过。。。
    ```java
    @Validated ValidBo bo
    ```
    amwyyyy
        7
    amwyyyy  
       Mar 28, 2019
    我这样用没遇到你的问题,建议检查下 Spring 与 SpringMVC 父子容器之间的关系,检查下注解扫描是否有重叠。
    airfling
        8
    airfling  
       Mar 28, 2019 via Android
    不建议你这样对界面参数进行检查,而且 @validated 你加在 controller 层面会在传参数的时候,进行 poro 绑定也就是把 controller 的 set 方法全部执行一边,这样 autowared 进来的自然就是 null 了,@validated 这个注解应该加在 poro 就是普通的参数封装类里面
    kosmosr
        9
    kosmosr  
       Mar 28, 2019 via Android
    姿势问题
    anzu
        11
    anzu  
    OP
       Mar 28, 2019
    不觉得用法有错。如果接口只有一两个参数,为什么仅仅为了做验证还要费力写个多余的 Class ?
    官方示例是放置在 Service,并没有限定使用范围
    https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-validation.html
    也有教程放置在 Controller,没什么区别
    https://www.mkyong.com/spring-boot/spring-rest-validation-example/
    galaxyyao
        12
    galaxyyao  
       Mar 28, 2019   ❤️ 3
    比较奇怪的应该是为什么要把 Controller 方法标记为 private 吧。Spring 的开发估计没预料到会有人这么做吧。。。
    我不记得哪个官方范例里曾经这么做过。

    https://stackoverflow.com/questions/17573742/best-practice-to-keep-method-visibility-with-spring-controller-stereotype
    这篇回答里有人提到虽然可以这么做,但会被 IDE 认为方法不被使用而标记为灰色,也会无法进行单元测试。
    我也想不出将方法设为 private 有什么好处。如果 LZ 有什么特殊意图的话可以补充一下。

    另外官方的范例是把验证注解放在实体类上的:
    https://spring.io/guides/gs/validating-form-input/
    对应 @RequestBody+POST 方法接收参数。以 LZ 的范例而言,name 中很可能包含特殊字符,放在 pathvariable 或 requestparam 中都可能会引起异常,所以从避免 bug 的角度,改为 @RequestBody+实体类验证的话会比较合适。(并不是说不能用 RequestParam )
    我个人写的时候,RequestParam 一般只接收主键或 id 之类的单个参数,一般也懒得加校验了。查不出来资源就返回 null 对象也符合 restful 的设计。
    Kyle18Tang
        13
    Kyle18Tang  
       Mar 28, 2019 via Android
    controller 方法用 private 的??我一直用 public。。。没出现过这问题。
    watzds
        14
    watzds  
       Mar 28, 2019 via Android
    接口还 private,这藏得死死的哈哈还叫接口吗
    wc951
        16
    wc951  
       Mar 28, 2019 via Android
    控制变量法呗,把 validated 注解去掉看能不能注入
    Infernalzero
        17
    Infernalzero  
       Mar 29, 2019   ❤️ 5
    因为你加了 @Validated,所以会触发 MethodValidationPostProcessor 的 postProcess 逻辑,然后 getbean 的对象都变成了 cglib 创建的代理了,因为是 cglib 创建的代理类,所以那个 field 是 null
    这里还有个原因就是因为你的这个方法是 private 的,如果是 public 的情况,cglib 创建的代理可以拦截这个方法,看下 CglibAopProxy 里的 DynamicAdvisedInterceptor 这个类的拦截实现就明白了,会取 targetSource 来调用,而 targetSource 就是原本的对象,field 就不是 null 了,但如果方法是 private 的情况就无法拦截直接调用代理类的方法了
    charles2java
        18
    charles2java  
       Mar 29, 2019 via Android
    private 当然不能在 controller 修饰对外方法,自己用法不规范
    anzu
        19
    anzu  
    OP
       Mar 29, 2019
    用 private 是因为以前刚开始学习 Java 的时候不知在哪里看到过,最佳实践是尽量给方法加上最严格访问权限,保证安全性和封装性。所以自己写之前随便一想好像 Controller 也没有被其它地方调用,顺手就写了 private。

    Java 的注解、反射、切面编程带来方便之余,也破坏了代码的封装性和安全性,扰乱了代码正常执行流程,使 Bug 更难追踪。

    关键在于,在没有加入 @ Validated 注解之前程序一切正常,令我很疑惑。
    quickma
        20
    quickma  
       Mar 29, 2019
    你都已经用框架了,还考虑 bug 更难追踪、代码正常执行流程??? spring boot 的启动流程去看个 10 遍你也不知道是怎么执行的。。。
    zhazi
        21
    zhazi  
       Mar 29, 2019
    访问修饰符不提供安全性
    zhazi
        22
    zhazi  
       Mar 29, 2019
    @charles2java 请教一下,哪个方法是对外的
    hmellochan
        23
    hmellochan  
       Mar 29, 2019
    controller 就是用来给外部访问的,还 private,另外 private 并不具有安全性,反射可解。
    Seney
        24
    Seney  
       Mar 29, 2019
    可以用 aop 拦截请求做校验 这样解耦 又不是侵入式的
    thinkmore
        25
    thinkmore  
       Apr 1, 2019
    @Infernalzero 在理,表示赞同
    imcoming
        26
    imcoming  
       Apr 2, 2019
    没有用的代码为什么要写出来,写出来还加上 private 保证不被访问,不是脱了裤子放屁么
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2508 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 92ms · UTC 12:16 · PVG 20:16 · LAX 05:16 · JFK 08:16
    ♥ Do have faith in what you're doing.