記一次Controller改造,及SpringMVC處理流程

概述

因爲工做須要,需實現這樣一個功能的controller框架:spring

1,Restful APImvc

2,請求參數校驗(請求中須要攜帶指定的參數,才能進入控制器方法。一次請求會攜帶一些基本信息,以及請求數據,此處校驗的是請求數據的攜帶狀況)框架

3,請求格式校驗(請求格式須要符合規定,才能進入控制器方法。此處校驗的是基本信息的攜帶狀況)工具

4,數據綁定(經過@RequestBody註解能直接綁定請求數據到POJO中。此POJO有一些字段,用以存儲請求的基本信息,以及一個Map,用以存儲請求數據)加密

5,請求數據的解密和返回數據的加密url

探索之旅

第一思路

開始我對spring mvc的請求流程不太熟悉,我構思,先通過HttpMessageConverter,再通過Intercepter。spa

由前者解析請求數據,轉換爲咱們自定義的POJO,而且解密請求數據。後者作格式校驗,參數校驗。很完美。code

但實際狀況是,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的官方文檔裏寫的不是很清楚,仍是要走一走源碼才能搞明白,感受有待完善。

相關文章
相關標籤/搜索