记一次Controller改造,及SpringMVC处理流程
概述
由于工作需要,需实现这样一个功能的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的官方文档里写的不是很清楚,还是要走一走源码才能搞明白,感觉有待完善。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里P8架构师深度概述分布式架构
简介 作为一名架构师,我们要专业,要能看懂代码,及时光着臂膀去机房,也能独挡一面!及时同事搞不定问题,或者撂挑子,你也能给老大一个坚定的眼神:不怕,有我在!还能在会议室上滔滔不绝,如若无人,让不懂技术的妹子看你时眼神迷离,就好想落霞与孤鹜齐飞! 分布式架构是一个非常复杂的体系,任何技术都不是孤立的存在,任何技术都无法适应所有场景。作为一名分布式系统架构或者资深研发人员,我们必须尽可能多的学习与之相关的各种知识,掌握各种技术的演进路线,正式从一名码农蜕变成为架构师 什么是分布式? 互联网应用的特点是:高并发,海量数据。互联网应用的用户数是没有上限的(取决于其开放特性),这也是和传统应用的本质区别。高并发指系统单位时间内收到的请求数量(取决于使用的用户数),没有上限。海量数据包括:海量数据的存储和海量数据的处理。这两个工程难题都可以使用分布式系统来解决。 简单理解,分布式系统就是把一些计算机通过网络连接起来,然后协同工作。协同工作需要解决两个问题: 1)任务分解 把一个问题拆解成若干个独立任务,每个任务在一台节点上运行,实现多任务的并发执行。 2)节点通信 节点之间互相通信,需要设计特定的...
- 下一篇
用网关zuul时,熔断hytrix里面的坑
1,zuul 默认的隔离级别是信号量,默认最大隔离信号量是100 信号量隔离和线程池隔离的区别如下: https://my.oschina.net/u/867417/blog/2120713 默认设置: 2,zuul里隔离是按服务隔离的,也就是1个服务1个信号量,非接口级别的 所以得注意zuul服务本身的线程池大小,后端服务的线程池大小,以及隔离信号量或线程池的线程池大小,防止1个线程被占用光 3,在zuul里,重新封装了hytrix的一些配置名称,导致hytrix的一些原生配置会失效 具体设置hytrix参数的setter如下: 需要通过zuulProperties重新设置的属性如下: 隔离级别指定:zuul.ribbonIsolationStrategy: SEMAPHORE 信号隔离的默认隔离大小:semaphore.maxSemaphores = 20 指定服务的信号隔离级别大小:zuul.eureka.serviceId.semaphore.maxSemaphores = 20 而原生的hytrix.command.default.execution.isolation...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Red5直播服务器,属于Java语言的直播服务器
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程