代码并不复杂,写完 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 方法无法被代理。希望知道确切原因的好心人能讲解一下,谢谢。
1
lufer 2019-03-28 17:22:04 +08:00
valid 注解为什么不加在参数前边,另外接口多参数的话你写个 vm 统一校验不会优雅很多吗
|
2
MoHen9 2019-03-28 17:30:24 +08:00 via Android
Validated 是加在 VO 和接口方法上的,你加在 controller 是不对的。
|
3
MoHen9 2019-03-28 17:32:28 +08:00 via Android
建议点进去看注释说明
|
4
gaius 2019-03-28 17:35:13 +08:00
你在哪看的加到 controller 上
|
6
mushishi 2019-03-28 17:47:38 +08:00
我一般是用在 BO 里面,然后 controller 里面接收 Bo 做基本的参数校验,隐约记得是不能放在 private 方法上的,没研究过。。。
```java @Validated ValidBo bo ``` |
7
amwyyyy 2019-03-28 17:53:12 +08:00
我这样用没遇到你的问题,建议检查下 Spring 与 SpringMVC 父子容器之间的关系,检查下注解扫描是否有重叠。
|
8
airfling 2019-03-28 18:04:13 +08:00 via Android
不建议你这样对界面参数进行检查,而且 @validated 你加在 controller 层面会在传参数的时候,进行 poro 绑定也就是把 controller 的 set 方法全部执行一边,这样 autowared 进来的自然就是 null 了,@validated 这个注解应该加在 poro 就是普通的参数封装类里面
|
9
kosmosr 2019-03-28 18:10:12 +08:00 via Android
姿势问题
|
10
zhazi 2019-03-28 18:10:17 +08:00 1
|
11
anzu OP 不觉得用法有错。如果接口只有一两个参数,为什么仅仅为了做验证还要费力写个多余的 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/ |
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 的设计。 |
13
Kyle18Tang 2019-03-28 18:48:18 +08:00 via Android
controller 方法用 private 的??我一直用 public。。。没出现过这问题。
|
14
watzds 2019-03-28 19:33:12 +08:00 via Android
接口还 private,这藏得死死的哈哈还叫接口吗
|
15
Allianzcortex 2019-03-28 19:45:21 +08:00
真是好问题. 之前开发 LaraForum 时也用的是 Aspectj 的 @PointCut 做 AOP 拦截( https://github.com/Allianzcortex/LaraForum/blob/master/src/main/java/com/laraforum/authorization/RolesAndPermissionsChecker.java) ,在 Controller 层和 @Validated 一样直接加注解就可以判断( https://github.com/Allianzcortex/LaraForum/blob/104b16fcd8847aa84bb9f5947d57f8c848fb3e5a/src/main/java/com/laraforum/controller/OtherController.java#L71)。楼主这个问题我猜这个链接有关: https://stackoverflow.com/questions/4402009/aspectj-and-catching-private-or-inner-methods, 但也没看过 Validated 源码就不能细说了
|
16
wc951 2019-03-28 20:29:47 +08:00 via Android
控制变量法呗,把 validated 注解去掉看能不能注入
|
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 的情况就无法拦截直接调用代理类的方法了 |
18
charles2java 2019-03-29 00:13:03 +08:00 via Android
private 当然不能在 controller 修饰对外方法,自己用法不规范
|
19
anzu OP 用 private 是因为以前刚开始学习 Java 的时候不知在哪里看到过,最佳实践是尽量给方法加上最严格访问权限,保证安全性和封装性。所以自己写之前随便一想好像 Controller 也没有被其它地方调用,顺手就写了 private。
Java 的注解、反射、切面编程带来方便之余,也破坏了代码的封装性和安全性,扰乱了代码正常执行流程,使 Bug 更难追踪。 关键在于,在没有加入 @ Validated 注解之前程序一切正常,令我很疑惑。 |
20
x7395759 2019-03-29 10:41:55 +08:00
你都已经用框架了,还考虑 bug 更难追踪、代码正常执行流程??? spring boot 的启动流程去看个 10 遍你也不知道是怎么执行的。。。
|
21
zhazi 2019-03-29 11:12:40 +08:00
访问修饰符不提供安全性
|
22
zhazi 2019-03-29 11:14:06 +08:00
@charles2java 请教一下,哪个方法是对外的
|
23
hmellochan 2019-03-29 11:53:53 +08:00
controller 就是用来给外部访问的,还 private,另外 private 并不具有安全性,反射可解。
|
24
Seney 2019-03-29 12:29:45 +08:00
可以用 aop 拦截请求做校验 这样解耦 又不是侵入式的
|
25
thinkmore 2019-04-01 14:51:22 +08:00
@Infernalzero 在理,表示赞同
|
26
imcoming 2019-04-02 10:23:00 +08:00
没有用的代码为什么要写出来,写出来还加上 private 保证不被访问,不是脱了裤子放屁么
|