Spring Boot 一小坑浪费一下午,虽然解决了但还是不知道原因 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
anzu
V2EX    Java

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

  •  
  •   anzu 2019-03-28 17:06:34 +08:00 5530 次点击
    这是一个创建于 2408 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码并不复杂,写完 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 条回复    2019-04-02 10:23:00 +08:00
    lufer
        1
    lufer  
       2019-03-28 17:22:04 +08:00
    valid 注解为什么不加在参数前边,另外接口多参数的话你写个 vm 统一校验不会优雅很多吗
    MoHen9
        2
    MoHen9  
       2019-03-28 17:30:24 +08:00 via Android
    Validated 是加在 VO 和接口方法上的,你加在 controller 是不对的。
    MoHen9
        3
    MoHen9  
       2019-03-28 17:32:28 +08:00 via Android
    建议点进去看注释说明
    gaius
        4
    gaius  
       2019-03-28 17:35:13 +08:00
    你在哪看的加到 controller 上
    jinue9900
        5
    jinue9900  
       2019-03-28 17:40:26 +08:00
    参数校验用的是 @Valid
    mushishi
        6
    mushishi  
       2019-03-28 17:47:38 +08:00
    我一般是用在 BO 里面,然后 controller 里面接收 Bo 做基本的参数校验,隐约记得是不能放在 private 方法上的,没研究过。。。
    ```java
    @Validated ValidBo bo
    ```
    amwyyyy
        7
    amwyyyy  
       2019-03-28 17:53:12 +08:00
    我这样用没遇到你的问题,建议检查下 Spring 与 SpringMVC 父子容器之间的关系,检查下注解扫描是否有重叠。
    airfling
        8
    airfling  
       2019-03-28 18:04:13 +08:00 via Android
    不建议你这样对界面参数进行检查,而且 @validated 你加在 controller 层面会在传参数的时候,进行 poro 绑定也就是把 controller 的 set 方法全部执行一边,这样 autowared 进来的自然就是 null 了,@validated 这个注解应该加在 poro 就是普通的参数封装类里面
    kosmosr
        9
    kosmosr  
       2019-03-28 18:10:12 +08:00 via Android
    姿势问题
    zhazi
        10
    zhazi  
       2019-03-28 18:10:17 +08:00   1
    anzu
        11
    anzu  
    OP
       2019-03-28 18:26:23 +08:00
    不觉得用法有错。如果接口只有一两个参数,为什么仅仅为了做验证还要费力写个多余的 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  
       2019-03-28 18:46:54 +08:00   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  
       2019-03-28 18:48:18 +08:00 via Android
    controller 方法用 private 的??我一直用 public。。。没出现过这问题。
    watzds
        14
    watzds  
       2019-03-28 19:33:12 +08:00 via Android
    接口还 private,这藏得死死的哈哈还叫接口吗
    wc951
        16
    wc951  
       2019-03-28 20:29:47 +08:00 via Android
    控制变量法呗,把 validated 注解去掉看能不能注入
    Infernalzero
        17
    Infernalzero  
       2019-03-29 00:07:13 +08:00   5
    因为你加了 @Validated,所以会触发 MethodValidationPostProcessor 的 postProcess 逻辑,然后 getbean 的对象都变成了 cglib 创建的代理了,因为是 cglib 创建的代理类,所以那个 field 是 null
    这里还有个原因就是因为你的这个方法是 private 的,如果是 public 的情况,cglib 创建的代理可以拦截这个方法,看下 CglibAopProxy 里的 DynamicAdvisedInterceptor 这个类的拦截实现就明白了,会取 targetSource 来调用,而 targetSource 就是原本的对象,field 就不是 null 了,但如果方法是 private 的情况就无法拦截直接调用代理类的方法了
    charles2java
        18
    charles2java  
       2019-03-29 00:13:03 +08:00 via Android
    private 当然不能在 controller 修饰对外方法,自己用法不规范
        19
    anzu  
    OP
       2019-03-29 10:01:52 +08:00
    用 private 是因为以前刚开始学习 Java 的时候不知在哪里看到过,最佳实践是尽量给方法加上最严格访问权限,保证安全性和封装性。所以自己写之前随便一想好像 Controller 也没有被其它地方调用,顺手就写了 private。

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

    关键在于,在没有加入 @ Validated 注解之前程序一切正常,令我很疑惑。
    anzu
    quickma
        20
    quickma  
       2019-03-29 10:41:55 +08:00
    你都已经用框架了,还考虑 bug 更难追踪、代码正常执行流程??? spring boot 的启动流程去看个 10 遍你也不知道是怎么执行的。。。
    zhazi
        21
    zhazi  
       2019-03-29 11:12:40 +08:00
    访问修饰符不提供安全性
    zhazi
        22
    zhazi  
       2019-03-29 11:14:06 +08:00
    @charles2java 请教一下,哪个方法是对外的
    hmellochan
        23
    hmellochan  
       2019-03-29 11:53:53 +08:00
    controller 就是用来给外部访问的,还 private,另外 private 并不具有安全性,反射可解。
    Seney
        24
    Seney  
       2019-03-29 12:29:45 +08:00
    可以用 aop 拦截请求做校验 这样解耦 又不是侵入式的
    thinkmore
        25
    thinkmore  
       2019-04-01 14:51:22 +08:00
    @Infernalzero 在理,表示赞同
    imcoming
        26
    imcoming  
       2019-04-02 10:23:00 +08:00
    没有用的代码为什么要写出来,写出来还加上 private 保证不被访问,不是脱了裤子放屁么
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5267 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 01:28 PVG 09:28 LAX 18:28 JFK 21:28
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86