Xc's Blog

记一次遇到的Spring的坑

2018/09/15 Share

昨天我们使用spring mvc搭建的新系统遇到了一个bug。今天排查到了原因,感觉比较有意思,和大家分享一下。

背景

我们使用SpringMVC的Controller来接收参数,并且使用了注解@RequestAttribute。这个注解有两个作用:

  1. 根据参数从当前请求的HttpRequest的Attribute中获取需要的参数(这个参数是我们之前使用Interceptor注入的)
  2. 这个注解有个required属性,默认为true,这个属性会在传入的Attribute 为null时抛出异常(这个异常我们会包装成参数异常并且向客户端返回对应的错误消息)

依赖于第二点,我们没有进行空指针检查,只进行了业务维度的逻辑验证。

发现问题

在一次联调的时候突然发生了空指针的异常,当时我们需要的参数是一个map<Integer,Integer>,而客户端那边由于c#的一些神奇的优化,map变成了一个list,按照我们之前的预期,这种情况应该会产生异常,并且被我们的异常处理器捕获成参数异常。但实际却是没有在最开始的时候产生异常,而是代码进一步运行到了service,在使用null指针的时候报了NPE的错误。

排查结果

经过排查,这个异常是是由两个坑合起来产生的:

  • 坑1:
    Spring会convert你的源数据作为@RequestAttribute的最终值,比如你传入了一个string类型的”123”,而你需要的是int,这个值会变成int类型的123,而如果你传入的是一个Collection,而目标是个普通的object,他会把集合的第一个元素返回,然后进一步convert,作为你需要的参数。

  • 坑2:
    required属性的检查在convert参数之前做的,如果源数据不为null,那么就不会因为参数为null而报对应的错,即使被convert之后的参数是null。

而我们的源数据正是一个list,并且第一个元素为null,导致了convert前不为null,绕过了判空检查,但convert之后为null,导致了程序出现NPE错误。

因此我们不能依赖于框架@RequestAttribute的判空处理,现在解决方法有几个:
1.手动检查是否为空
2.自己定义一个annotation,然后继承RequestAttributeMethodArgumentResolver,把annotation换成自己的,然后在这个自定义的方法里,在获得参数后进行判空处理
3.使用spring aop在Controller层检查参数是否为空。

第一种会在代码里产生很多重复的非业务逻辑的空检查,我们把这个作为备选项。
第二种侵入性过强,我们先放弃了。
最终我们选择了第三种

结论

  1. @RequestAttribute的判空处理不可靠,必须再进行一次判空以保证传入的参数正确
  2. 有时不能过分信任框架

后记

给spring报了个bug,最后还是被关掉了。地址:https://jira.spring.io/browse/SPR-16438

CATALOG
  1. 1. 背景
  2. 2. 发现问题
  3. 3. 排查结果
  4. 4. 结论
  5. 5. 后记