记一次Controller改造,及SpringMVC处理流程


声明:本文转载自https://my.oschina.net/u/3039151/blog/2208563,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

概述

由于工作需要,需实现这样一个功能的controller框架:

1,Restful API

2,请求参数校验(请求中需要携带指定的参数,才能进入控制器方法。一次请求会携带一些基本信息,以及请求数据,此处校验的是请求数据的携带情况)

3,请求格式校验(请求格式需要符合规定,才能进入控制器方法。此处校验的是基本信息的携带情况)

4,数据绑定(通过@RequestBody注解能直接绑定请求数据到POJO中。此POJO有一些字段,用以存储请求的基本信息,以及一个Map,用以存储请求数据)

5,请求数据的解密和返回数据的加密

探索之旅

第一思路

开始我对spring mvc的请求流程不太熟悉,我构思,先经过HttpMessageConverter,再经过Intercepter。

由前者解析请求数据,转换为我们自定义的POJO,并且解密请求数据。后者做格式校验,参数校验。很完美。

但实际情况是,Intercepter在HttpMessageConverter之前执行。更具体地说,spring mvc的拦截器是在转换器外层的,也就是请求进来的时候,先进拦截器,再进转换器;返回的时候,先进转换器,再进拦截器。

第一思路GG

第二思路

第一思路不行了,我开始寻找spring mvc中能在转换器(HttpMessageConverter)之后打断整个请求流程的办法。

看了官方文档和一些博客,我找到了@ControllerAdvice这个注解,以及RequestBodyAdvice这个接口,该接口有四个方法:

boolean supports(MethodParameter methodParameter, Type targetType,
		Class<? extends HttpMessageConverter<?>> converterType);  


Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
		Type targetType, Class<? extends HttpMessageConverter<?>> converterType);  


HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
		Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;  


Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
		Type targetType, Class<? extends HttpMessageConverter<?>> converterType);  

乍一看,可以指定支持类型,afterBodyRead方法可以在HttpMessageConverter转换之后拿到POJO。好像很完美。

但afterBodyRead方法不能打断流程!!!这个方法没有抛出异常,也就是说,程序走到这里,不管你在afterBodyRead里做了什么,控制器方法都注定要被调用了,如此以来,参数校验和格式校验就无法实现。(如果你不用奇淫巧技的话,是这样。但其实你可以在这里抛出一个uncheck的Exception,然后使用@ControllerAdvice配合@ExceptionHandler注解打断流程,并跳到被@ExceptionHandler注释的控制器方法中。只是这个方法太极客而且丑陋了,我没用)

本来我想在HttpMessageConverter中转换一次POJO,整个程序就使用这个POJO,但这个愿望似乎是无法实现了。如果有高手知道解决办法,还望指出。

第二思路GG

第三思路

抛弃了“一次转换,终身使用”的执念,我开始思考,多次转换的解决办法

还记得第二思路里那个接口里的beforeBodyRead方法吗,它抛出了一个IOException!!!没错,你可以在这里转换数据,并校验参数和格式,抛出IOException,并配合@ControllerAdvice的@ExceptionHandler。

IOException!?哎,我是一个有代码洁癖的人,你要硬说请求格式错误,参数错误是一种IO错误,也没问题。你要硬把一堆验证代码塞在这个小小的beforeBodyRead方法里,也没问题。但是我拒绝。

第四思路

既然Spring mvc提供了拦截器,它就应该有用武之地,咱们再回过头来考虑一下它吧。

现在我已经摒弃了“一次转换,终身使用”的执念,那么让我来考虑一下,拦截器转换请求,并做格式校验,参数校验。

没有问题,只要你提供统一的解析工具和解密工具给拦截器。唯一的缺点是,我将请求格式校验,请求参数校验拆开成两个拦截器,如此,对Http请求的解析和转换将会发生两次。

如果用思路三的做法,这个多次转换就可以避免,但拦截器的url过滤,拦截器排序这样的功能就享受不到了。

对于校验发现请求错误,有两种办法做处理,1是抛出异常,要知道preHandle方法是throws Exception的,此时配合@ControllerAdvice的@ExceptionHandler来处理;2是当发生异常时,让preHandle返回false,返回前用request.getRequestDispatcher(...).forward(request, response)发起转发,你可以转发到一个你专门用来返回错误信息的控制器方法上。

最后我选择了思路四的方案,其实思路三也是完全没有问题的。如果有大神知道更好的方案,请指教。

对返回的处理

对返回数据的处理是比较简单的,没有这么多周折。利用spring mvc提供的@ResponseBody注解,写一个HttpMessageConverter就行,此处我使用了继承AbstractHttpMessageConverter的方法,较为简单。

值得一提的是,对控制器方法的返回值,在进入转换器之前,有两种办法去做统一的处理,1是@ControllerAdvice注解配合ResponseBodyAdvice接口;2是HandlerMethodReturnValueHandler。

如果你用方式2实现,要注意,HttpMessageConverter的调用需要你在HandlerMethodReturnValueHandler中手动实现,否则转换器不会被调用。例如spring mvc自己实现的AbstractMessageConverterMethodProcessor抽象类,就提供了writeWithMessageConverters()方法。该抽象类实现了HandlerMethodReturnValueHandler接口,继承自AbstractMessageConverterMethodArgumentResolver抽象类。

所以你如果实现自己的HandlerMethodReturnValueHandler,可以通过实现AbstractMessageConverterMethodProcessor抽象类。

我使用的是方式1。

最后在HttpMessageConverter中去做加密就OK了。

总结

spring mvc对流程的控制我知道的有3个。1是拦截器;2是@ControllerAdvice配合RequestBodyAdvice接口的beforeBodyRead方法抛出异常;3是HttpMessageConverter的readInternal方法抛出异常。除了1之外,都需要@ExceptionHandler做配合。(如果有其他方法,望指出)

转发的时候,新的请求会从最开始重走一遍,也就是说你的所有拦截器都会再走一遍;转换器,控制器增强器等等这些东西等于都是自动复用的。配置类中注册拦截器的InterceptorRegistry的addInterceptor方法返回的InterceptorRegistration实例有excludePathPatterns等方法,可以用来控制拦截器作用的url范围。

很多东西spring mvc的官方文档里写的不是很清楚,还是要走一走源码才能搞明白,感觉有待完善。

本文发表于2018年09月26日 01:00
(c)注:本文转载自https://my.oschina.net/u/3039151/blog/2208563,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1938 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1