詳解如何在SpringBoot中自定義參數(shù)解析器
前言
在一個(gè) Web 請(qǐng)求中,參數(shù)我們無非就是放在地址欄或者請(qǐng)求體中,個(gè)別請(qǐng)求可能放在請(qǐng)求頭中。
放在地址欄中,我們可以通過如下方式獲取參數(shù):
String?javaboy?=?request.getParameter("name?");
放在請(qǐng)求體中,如果是 key/value 形式,我們可以通過如下方式獲取參數(shù):
String?javaboy?=?request.getParameter("name?");
如果是 JSON 形式,我們則通過如果如下方式獲取到輸入流,然后解析成 JSON 字符串,再通過 JSON 工具轉(zhuǎn)為對(duì)象:
BufferedReader?reader?=?new?BufferedReader(new?InputStreamReader(request.getInputStream())); String?json?=?reader.readLine(); reader.close(); User?user?=?new?ObjectMapper().readValue(json,?User.class);
如果參數(shù)放在請(qǐng)求頭中,我們可以通過如下方式獲取:
String?javaboy?=?request.getHeader("name");
如果你用的是 Jsp/Servlet 那一套技術(shù)棧,那么參數(shù)獲取無外乎這幾種方式。
如果用了 SpringMVC 框架,有的小伙伴們可能會(huì)覺得參數(shù)獲取方式太豐富了,各種注解如 @RequestParam
、@RequestBody
、@RequestHeader
、@PathVariable
,參數(shù)可以是 key/value 形式,也可以是 JSON 形式,非常豐富!但是,無論多么豐富,最底層獲取參數(shù)的方式無外乎上面幾種。
那有小伙伴要問了,SpringMVC 到底是怎么樣從 request 中把參數(shù)提取出來直接給我們用的呢?例如下面這個(gè)接口:
@RestController public?class?HelloController?{ ????@GetMapping("/hello") ????public?String?hello(String?name)?{ ????????return?"hello?"+name; ????} }
我們都知道 name 參數(shù)是從 HttpServletRequest 中提取出來的,到底是怎么提取出來的?這就是松哥今天要和大家分享的話題。
1.自定義參數(shù)解析器
為了搞清楚這個(gè)問題,我們先來自定義一個(gè)參數(shù)解析器看看。
自定義參數(shù)解析器需要實(shí)現(xiàn) HandlerMethodArgumentResolver 接口,我們先來看看該接口:
public?interface?HandlerMethodArgumentResolver?{ ?boolean?supportsParameter(MethodParameter?parameter); ?@Nullable ?Object?resolveArgument(MethodParameter?parameter,?@Nullable?ModelAndViewContainer?mavContainer, ???NativeWebRequest?webRequest,?@Nullable?WebDataBinderFactory?binderFactory)?throws?Exception; }
這個(gè)接口中就兩個(gè)方法:
- supportsParameter:該方法表示是否啟用這個(gè)參數(shù)解析器,返回 true 表示啟用,返回 false 表示不啟用。
- resolveArgument:這是具體的解析過程,就是從 request 中取出參數(shù)的過程,方法的返回值就對(duì)應(yīng)了接口中參數(shù)的值。
自定義參數(shù)解析器只需要實(shí)現(xiàn)該接口即可。
假設(shè)我現(xiàn)在有這樣一個(gè)需求(實(shí)際上在 Spring Security 中獲取當(dāng)前登錄用戶名非常方便,這里只是為了該案例而做,勿抬杠):
假設(shè)我現(xiàn)在系統(tǒng)安全框架使用了 Spring Security,如果我在接口的參數(shù)上添加了 @CurrentUserName 注解,那么該參數(shù)的值就是當(dāng)前登錄的用戶名,像下面這樣:
@RestController public?class?HelloController?{ ????@GetMapping("/hello") ????public?String?hello(@CurrentUserName?String?name)?{ ????????return?"hello?"+name; ????} }
要實(shí)現(xiàn)這個(gè)功能,非常 easy,首先我們自定義一個(gè) @CurrentUserName
注解,如下:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public?@interface?CurrentUserName?{ }
這個(gè)注解沒啥好解釋的。
接下來我們自定義參數(shù)解析器 CurrentUserNameHandlerMethodArgumentResolver,如下:
public?class?CurrentUserNameHandlerMethodArgumentResolver?implements?HandlerMethodArgumentResolver?{ ????@Override ????public?boolean?supportsParameter(MethodParameter?parameter)?{ ????????return?parameter.getParameterType().isAssignableFrom(String.class)&¶meter.hasParameterAnnotation(CurrentUserName.class); ????} ????@Override ????public?Object?resolveArgument(MethodParameter?parameter,?ModelAndViewContainer?mavContainer,?NativeWebRequest?webRequest,?WebDataBinderFactory?binderFactory)?throws?Exception?{ ????????User?user?=?(User)?SecurityContextHolder.getContext().getAuthentication().getPrincipal(); ????????return?user.getUsername(); ????} }
- supportsParameter:如果參數(shù)類型是 String,并且參數(shù)上有
@CurrentUserName
注解,則使用該參數(shù)解析器。 - resolveArgument:該方法的返回值就是參數(shù)的具體值,當(dāng)前登錄用戶名從 SecurityContextHolder 中獲取即可
最后,我們?cè)賹⒆远x的參數(shù)解析器配置到 HandlerAdapter 中,配置方式如下:
@Configuration public?class?WebConfig?implements?WebMvcConfigurer?{ ????@Override ????public?void?addArgumentResolvers(List<HandlerMethodArgumentResolver>?resolvers)?{ ????????resolvers.add(new?CurrentUserNameHandlerMethodArgumentResolver()); ????} }
至此,就算配置完成了。
接下來啟動(dòng)項(xiàng)目,用戶登錄成功后,訪問 /hello
接口,就可以看到返回當(dāng)前登錄用戶數(shù)據(jù)了。
這就是我們自定義的一個(gè)參數(shù)類型解析器??梢钥吹?,非常 Easy。
在 SpringMVC 中,默認(rèn)也有很多 HandlerMethodArgumentResolver 的實(shí)現(xiàn)類,他們處理的問題也都類似,松哥再給大家舉個(gè)例子。
2.PrincipalMethodArgumentResolver
如果我們?cè)陧?xiàng)目中使用了 Spring Security,我們可以通過如下方式獲取當(dāng)前登錄用戶信息:
@GetMapping("/hello2") public?String?hello2(Principal?principal)?{ ????return?"hello?"?+?principal.getName(); }
即直接在當(dāng)前接口的參數(shù)中添加 Principal 類型的參數(shù)即可,該參數(shù)描述了當(dāng)前登錄用戶信息,這個(gè)用過 Spring Security 的小伙伴應(yīng)該都知道
那么這個(gè)功能是怎么實(shí)現(xiàn)的呢?當(dāng)然就是 PrincipalMethodArgumentResolver 在起作用了!
我們一起來看下這個(gè)參數(shù)解析器:
public?class?PrincipalMethodArgumentResolver?implements?HandlerMethodArgumentResolver?{ ?@Override ?public?boolean?supportsParameter(MethodParameter?parameter)?{ ??return?Principal.class.isAssignableFrom(parameter.getParameterType()); ?} ?@Override ?public?Object?resolveArgument(MethodParameter?parameter,?@Nullable?ModelAndViewContainer?mavContainer, ???NativeWebRequest?webRequest,?@Nullable?WebDataBinderFactory?binderFactory)?throws?Exception?{ ??HttpServletRequest?request?=?webRequest.getNativeRequest(HttpServletRequest.class); ??if?(request?==?null)?{ ???throw?new?IllegalStateException("Current?request?is?not?of?type?HttpServletRequest:?"?+?webRequest); ??} ??Principal?principal?=?request.getUserPrincipal(); ??if?(principal?!=?null?&&?!parameter.getParameterType().isInstance(principal))?{ ???throw?new?IllegalStateException("Current?user?principal?is?not?of?type?["?+ ?????parameter.getParameterType().getName()?+?"]:?"?+?principal); ??} ??return?principal; ?} }
- supportsParameter:這個(gè)方法主要是判斷參數(shù)類型是不是 Principal,如果參數(shù)類型是 Principal,就支持。
- resolveArgument:這個(gè)方法的邏輯很簡(jiǎn)單,首先獲取原生的請(qǐng)求,再?gòu)恼?qǐng)求中獲取 Principal 對(duì)象返回即可。
是不是很簡(jiǎn)單,有了這個(gè),我們就可以隨時(shí)加載到當(dāng)前登錄用戶信息了。
3.RequestParamMapMethodArgumentResolver
松哥再給大家舉個(gè)例子:
@RestController public?class?HelloController?{ ????@PostMapping("/hello") ????public?void?hello(@RequestParam?MultiValueMap?map)?throws?IOException?{ ????????//省略... ????} }
這個(gè)接口很多小伙伴可能都寫過,使用 Map 去接收前端傳來的參數(shù),那么這里用到的參數(shù)解析器就是 RequestParamMapMethodArgumentResolver。
public?class?RequestParamMapMethodArgumentResolver?implements?HandlerMethodArgumentResolver?{ ?@Override ?public?boolean?supportsParameter(MethodParameter?parameter)?{ ??RequestParam?requestParam?=?parameter.getParameterAnnotation(RequestParam.class); ??return?(requestParam?!=?null?&&?Map.class.isAssignableFrom(parameter.getParameterType())?&& ????!StringUtils.hasText(requestParam.name())); ?} ?@Override ?public?Object?resolveArgument(MethodParameter?parameter,?@Nullable?ModelAndViewContainer?mavContainer, ???NativeWebRequest?webRequest,?@Nullable?WebDataBinderFactory?binderFactory)?throws?Exception?{ ??ResolvableType?resolvableType?=?ResolvableType.forMethodParameter(parameter); ??if?(MultiValueMap.class.isAssignableFrom(parameter.getParameterType()))?{ ???//?MultiValueMap ???Class<?>?valueType?=?resolvableType.as(MultiValueMap.class).getGeneric(1).resolve(); ???if?(valueType?==?MultipartFile.class)?{ ????MultipartRequest?multipartRequest?=?MultipartResolutionDelegate.resolveMultipartRequest(webRequest); ????return?(multipartRequest?!=?null???multipartRequest.getMultiFileMap()?:?new?LinkedMultiValueMap<>(0)); ???} ???else?if?(valueType?==?Part.class)?{ ????HttpServletRequest?servletRequest?=?webRequest.getNativeRequest(HttpServletRequest.class); ????if?(servletRequest?!=?null?&&?MultipartResolutionDelegate.isMultipartRequest(servletRequest))?{ ?????Collection<Part>?parts?=?servletRequest.getParts(); ?????LinkedMultiValueMap<String,?Part>?result?=?new?LinkedMultiValueMap<>(parts.size()); ?????for?(Part?part?:?parts)?{ ??????result.add(part.getName(),?part); ?????} ?????return?result; ????} ????return?new?LinkedMultiValueMap<>(0); ???} ???else?{ ????Map<String,?String[]>?parameterMap?=?webRequest.getParameterMap(); ????MultiValueMap<String,?String>?result?=?new?LinkedMultiValueMap<>(parameterMap.size()); ????parameterMap.forEach((key,?values)?->?{ ?????for?(String?value?:?values)?{ ??????result.add(key,?value); ?????} ????}); ????return?result; ???} ??} ??else?{ ???//?Regular?Map ???Class<?>?valueType?=?resolvableType.asMap().getGeneric(1).resolve(); ???if?(valueType?==?MultipartFile.class)?{ ????MultipartRequest?multipartRequest?=?MultipartResolutionDelegate.resolveMultipartRequest(webRequest); ????return?(multipartRequest?!=?null???multipartRequest.getFileMap()?:?new?LinkedHashMap<>(0)); ???} ???else?if?(valueType?==?Part.class)?{ ????HttpServletRequest?servletRequest?=?webRequest.getNativeRequest(HttpServletRequest.class); ????if?(servletRequest?!=?null?&&?MultipartResolutionDelegate.isMultipartRequest(servletRequest))?{ ?????Collection<Part>?parts?=?servletRequest.getParts(); ?????LinkedHashMap<String,?Part>?result?=?CollectionUtils.newLinkedHashMap(parts.size()); ?????for?(Part?part?:?parts)?{ ??????if?(!result.containsKey(part.getName()))?{ ???????result.put(part.getName(),?part); ??????} ?????} ?????return?result; ????} ????return?new?LinkedHashMap<>(0); ???} ???else?{ ????Map<String,?String[]>?parameterMap?=?webRequest.getParameterMap(); ????Map<String,?String>?result?=?CollectionUtils.newLinkedHashMap(parameterMap.size()); ????parameterMap.forEach((key,?values)?->?{ ?????if?(values.length?>?0)?{ ??????result.put(key,?values[0]); ?????} ????}); ????return?result; ???} ??} ?} }
- supportsParameter:參數(shù)類型是 Map,并且使用了
@RequestParam
注解,并且@RequestParam
注解中沒有配置 name 屬性,就可以使用該參數(shù)解析器。 - resolveArgument:具體解析分為兩種情況:MultiValueMap 和其他 Map,前者中又分三種情況:MultipartFile、Part 或者其他普通請(qǐng)求,前兩者可以處理文件上傳,第三個(gè)就是普通參數(shù)。如果是普通 Map,則直接獲取到原始請(qǐng)求參數(shù)放到一個(gè) Map 集合中返回即可。
4.小結(jié)
前面和大家聊的都是幾種簡(jiǎn)單的情況,還有復(fù)雜的如 PathVariableMethodArgumentResolver 和 RequestParamMethodArgumentResolver 松哥以后再和大家詳細(xì)聊。
以上就是詳解如何在SpringBoot中自定義參數(shù)解析器的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot自定義參數(shù)解析器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot多模塊化整合mybatis,mapper自動(dòng)注入失敗問題及解決
這篇文章主要介紹了springboot多模塊化整合mybatis,mapper自動(dòng)注入失敗問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Spring注解@Transactional失效的場(chǎng)景分析
在使用Spring中事務(wù)注解@Transactional時(shí)會(huì)存在幾種場(chǎng)景下該注解失效,即不能按照預(yù)期封裝成一個(gè)事務(wù)操作,于是本文文將對(duì)該注解進(jìn)行學(xué)習(xí)并對(duì)相關(guān)失效場(chǎng)景進(jìn)行分析,需要的朋友可以參考下2023-07-07利用openoffice+jodconverter-code-3.0-bate4實(shí)現(xiàn)ppt轉(zhuǎn)圖片
這篇文章主要為大家詳細(xì)介紹了利用openoffice+jodconverter-code-3.0-bate4實(shí)現(xiàn)ppt轉(zhuǎn)圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07Springboot 項(xiàng)目讀取Resources目錄下的文件(推薦)
這篇文章主要介紹了Springboot 項(xiàng)目讀取Resources目錄下的文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11java前后端傳值,參數(shù)有集合類型的數(shù)據(jù)時(shí)的兩種操作方式
這篇文章主要介紹了java前后端傳值,參數(shù)有集合類型的數(shù)據(jù)時(shí)的兩種操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Redisson 分布式延時(shí)隊(duì)列 RedissonDelayedQueue 運(yùn)行流程
這篇文章主要介紹了Redisson分布式延時(shí)隊(duì)列 RedissonDelayedQueue運(yùn)行流程,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09詳解Java數(shù)據(jù)結(jié)構(gòu)之平衡二叉樹
平衡二叉樹(Balanced?Binary?Tree)又被稱為AVL樹(有別于AVL算法),且具有以下性質(zhì):它是一?棵空樹或它的左右兩個(gè)子樹的高度差的絕對(duì)值不超過1,并且左右兩個(gè)子樹都是一棵平衡二叉樹。本文將詳解介紹一下平衡二叉樹的原理與實(shí)現(xiàn),需要的可以參考一下2022-02-02Apache?Commons?CLI構(gòu)建命令行應(yīng)用利器教程
這篇文章主要為大家介紹了構(gòu)建命令行應(yīng)用利器Apache?Commons?CLI的使用教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12IDEA下Maven的pom文件導(dǎo)入依賴出現(xiàn)Auto build completed with errors的問題
這篇文章主要介紹了IDEA下Maven的pom文件導(dǎo)入依賴出現(xiàn)Auto build completed with errors,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06