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

2019-03-28 17:06:34 +08:00
 anzu

代码并不复杂,写完 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 方法无法被代理。希望知道确切原因的好心人能讲解一下,谢谢。

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

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

关键在于,在没有加入 @ Validated 注解之前程序一切正常,令我很疑惑。
x7395759
2019-03-29 10:41:55 +08:00
你都已经用框架了,还考虑 bug 更难追踪、代码正常执行流程??? spring boot 的启动流程去看个 10 遍你也不知道是怎么执行的。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/549604

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX