文档章节

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

wanxiangming
 wanxiangming
发布于 2018/09/25 19:05
字数 1709
阅读 743
收藏 10

概述

由于工作需要,需实现这样一个功能的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的官方文档里写的不是很清楚,还是要走一走源码才能搞明白,感觉有待完善。

© 著作权归作者所有

wanxiangming
粉丝 3
博文 24
码字总数 38833
作品 0
东城
私信 提问
Spring源码分析之WebMVC

作者: 一字马胡 转载标志 【2018-01-07】 更新日志 导入 Spring源码分析系列文章索引: Spring源码分析之Bean的解析 Spring源码分析之Bean的加载 Spring源码分析之AOP 本文是系列文章的第四...

疼丸李白
2018/01/07
0
0
如何伪装成一个服务端开发(七)

目录 如何伪装成一个服务端开发(一) 如何伪装成一个服务端开发(二) 如何伪装成一个服务端开发(三) 如何伪装成一个服务端开发(四) 如何伪装成一个服务端开发(五) 如何伪装成一个服务端开发(六...

街角的小丑
01/04
36
0
构建SpringMVC应用的两种方法

dispatchServlet是Spring MVC的核心。 一次Spring NVC请求的流转过程如下: 请求到达前端控制器DispatcherServlet,他的作用是将请求转发给相应的控制器(controller)。 DispatcherServlet通...

你好路小雨
2018/12/18
0
0
自己手写一个SpringMVC框架

前端框架很多,但没有一个框架称霸,后端框架现在Spring已经完成大一统。所以学习Spring是Java程序员的必修课。 Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的...

yzbty23
01/30
58
0
Spring MVC HTTP Message Conversion

Spring MVC HTTP Message Conversion 在spring mvc中,一个http请求和响应流程如下图所示: 其中 HttpMessageConvert 扮演了 http请求消息和响应消息进行转换和角色。 这里先说一下对于 HTTP...

秋风醉了
2015/07/19
206
0

没有更多内容

加载失败,请刷新页面

加载更多

聊聊Tomcat中的连接器(Connector)

上期回顾 上一篇文章《Tomcat在SpringBoot中是如何启动的》从main方法启动说起,窥探了SpringBoot是如何启动Tomcat的,在分析Tomcat中我们重点提到了,Tomcat主要包括2个组件,连接器(Conne...

木木匠
26分钟前
1
0
OSChina 周一乱弹 —— 熟悉的味道,难道这就是恋爱的感觉

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @xiaoshiyue :好久没分享歌了分享张碧晨的单曲《今后我与自己流浪》 《今后我与自己流浪》- 张碧晨 手机党少年们想听歌,请使劲儿戳(这里)...

小小编辑
今天
1K
19
SpringBoot中 集成 redisTemplate 对 Redis 的操作(二)

SpringBoot中 集成 redisTemplate 对 Redis 的操作(二) List 类型的操作 1、 向列表左侧添加数据 Long leftPush = redisTemplate.opsForList().leftPush("name", name); 2、 向列表右......

TcWong
今天
28
0
排序––快速排序(二)

根据排序––快速排序(一)的描述,现准备写一个快速排序的主体框架: 1、首先需要设置一个枢轴元素即setPivot(int i); 2、然后需要与枢轴元素进行比较即int comparePivot(int j); 3、最后...

FAT_mt
昨天
4
0
mysql概览

学习知识,首先要有一个总体的认识。以下为mysql概览 1-架构图 2-Detail csdn |简书 | 头条 | SegmentFault 思否 | 掘金 | 开源中国 |

程序员深夜写bug
昨天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部