0x0. LocalDateTime在SpringBoot中的窘境
0x1. 初步解决LocalDateTime作为query参数的问题
经过多方面查询得知,对于java8时间格式的问题,其实困扰着很多人,网上也提供了很多的解决方案,比如说下面这种在query参数上加注解的方式:
1 |
|
请求结果如下:
但是这样造成的后果是严重影响了代码的质量,每个使用LocalDateTime的参数的地方都需要使用两个注解。
0x2. 寻找全局解决方式
1 |
|
Controller层代码:
1 |
|
但是我使用过后却并没有得到我想要的效果,出现了报错,这是为什么?
得到的报错如下
java.lang.IllegalStateException: No primary or default constructor found for class java.time.LocalDateTime
,仔细想了一下为什么别人能使用呢,后面才发现自己缺少了一个注解@RequestParam,好的,我这就去加上:
1 |
|
请求结果:
nice!全局方式query也生效了,现在我们来尝试一下使用Model去接收参数:
TestModel:
1 |
|
Controller层:
1 |
|
请求结果:
嗯,现在已经大致满足要求了,但是对于简洁到了极致的自己来说却依旧不能感到满足,为什么LocalDateTime作为一个属性存在于Model中时却不需要使用注解方式去指定呢?而我们不使用@RequestParam时LocalDateTime却会出现报错?
0x3. 深入研究分析参数转换过程
我们先在先在Controller层下一个断点,然后查看调用堆栈,找到了一个可疑的方法:invokeForRequest
我们点进去看一看:
1 |
|
1 |
protected Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, |
1 |
|
我们展开参数解析器的列表:
1 |
this.argumentResolvers = {LinkedList@6957} size = 26 |
是不是看到了几个看着很眼熟的东西?
RequestParam
,
PathVariable
,
RequestBody
,在idea中查找RequestParamMethodArgumentResolver类,进入他的方法看一下,
1 |
|
这里可以基本确定一件事,在我这测试时默认的参数解析器有26个,每个参数解析器有着各自的用途,比如加了@RequestParam注解的参数会被RequestParamMethodArgumentResolver所解析,然后再查找类型有无相应的Convert转换器,在我们这里使用@Bean注入了一个Convert转换器,所以LocalDateTime能被正确的解析。
那么我们之前不加@RequestParam直接使用LocalDateTime作为参数的时候是被哪个解析器所捕获的呢,方法已经找到了,那么直接下个断点跑一遍查看result返回的是哪个解析器就知道了!
如果没记错ServletModelAttributeMethodProcessor是参数解析器的最后一个,我们进入到这个类看一眼,没有supportsParameter方法,但是他继承了ModelAttributeMethodProcessor,我们再进入他的父类中:
1 |
|
对于没有指定参数解析器的参数来说,默认指定的参数解析器是ModelAttributeMethodProcessor,并且由源码得知,如果加了@ModelAttribute注解,或者非简单属性则会被该解析器捕获,所以我们平时所使用的model去接收不需要加注解即可被正确的解析,既然都来了,那么顺便看一下resolveArgument方法是怎么解析参数的
1 |
|
继续跟进createAttribute方法,看看他是怎么将值绑定给对象的:
1 |
protected Object createAttribute(String attributeName, MethodParameter parameter, |
看到这里是不是发现报错的地方有点熟悉?这就是我们之前LocalDateTime抛出异常的地方了,可以得知对于对象的绑定,先通过BeanUtils获取主要的构造函数,如果获取不到,则使用反射的方式先尝试获取声明为public的无参构造函数,最后才会尝试使用getDeclaredConstructor获取所有的无参构造函数,但是对于LocalDateTime这种使用工厂构造不存在无参构造函数的类来说就会直接抛出NoSuchMethodException异常。那么如果我们不想使用@RequestParam注解加在参数上怎么办呢?
0x4. 创建参数解析器
1 |
|
0x5. 推荐使用注册Convert的方式
1 |
|
0x6. 小结
0xf. 附录
时间字符串转 LocalDateTime 工具类。转换不同时区的时间为本地时间(默认使用
ZoneId.systemDefault()
为本地时区)
yyyy-MM-dd HH:mm[:ss][.sss]
yyyy-MM-ddTHH:mm[:ss][.sss][Z|(+|-)HH:mm]
yyyy-MM-dd HH:mm[:ss][.sss][Z|(+|-)HH:mm]
1 |
/** |