springboot HandlerIntercepter攔截器修改request body數(shù)據(jù)的操作
實(shí)際工作中學(xué)習(xí)技術(shù)是最快、最深刻的。當(dāng)然,自身的持續(xù)學(xué)習(xí)意識(shí)是必須的
技術(shù)棧版本:
spring boot 2.0.2
遇到事兒了
近來做業(yè)務(wù)需求,前端同學(xué)fe將userId
和userName
放到request header
中了。
后端api接口要想使用userId
和userName
,每個(gè)接口都要從header
中獲取。
試想一下,如果你有十個(gè)接口,那么每個(gè)接口都要寫一遍
Object.setUserId(request.getHeader("userId"))
正如下面代碼段
@RestController @Validated @RequestMapping("/template") public class TemplateController { // 一個(gè)feign client @Autowired TemplateClient templateClient @PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE) public ResultVO create(@RequestBody @Valid TemplateParam param, HttpServletRequest request) { // 每個(gè)接口都要寫一遍setXXX()方法 param.setUserId(request.getHeader("userId")); param.setUserName(request.getHeader("userName")); return templateClient.createTemplate(param).toResultVO(); } } @Data public class TemplateParam{ private Long templateId; private Long userId; private String userName; }
解決辦法
大家都知道的兩大利器,
tomcat
的Filter
和spring
的Intercepter
(具體為HandlerIntercepter
)
實(shí)現(xiàn)原理
具體方法為定義一個(gè)Filter
實(shí)現(xiàn)類和一個(gè)HandlerIntercepter
的實(shí)現(xiàn)類。再定義一個(gè)HttpServletRequest
實(shí)現(xiàn)類,其作用分別為
Filter實(shí)現(xiàn)類:UserInfoFilter
創(chuàng)建一個(gè)入口,在這個(gè)入口中定義一個(gè)機(jī)會(huì):將我們自定義的CustomHttpServletRequestWrapper
代替HttpServletRequest
隨著請(qǐng)求傳遞下去
HttpServletRequest實(shí)現(xiàn)類:customHttpServletRequestWrapper
因?yàn)?code>HttpServletRequest對(duì)象的body
數(shù)據(jù)只能get
,不能set
,即不能再次賦值。
而我們的需求是需要給HttpServletRequest
賦值,所以需要定義一個(gè)HttpServletRequest
實(shí)現(xiàn)類:customHttpServletRequestWrapper
,這個(gè)實(shí)現(xiàn)類可以被賦值來滿足我們的需求。
HandlerIntercepter的實(shí)現(xiàn)類:CustomInterceptor
攔截請(qǐng)求,獲取接口方法相關(guān)信息(方法名,參數(shù),返回值等)。從而實(shí)現(xiàn)統(tǒng)一的給request body
動(dòng)態(tài)賦值
實(shí)現(xiàn)思路如上所述,具體的實(shí)現(xiàn)代碼如下
代碼實(shí)現(xiàn)
聲明基礎(chǔ)bean
: UserInfoParam
UserInfoParam
:定義了包含userId
,userName
的實(shí)體bean,要想將用戶信息注入進(jìn)入,需要入?yún)?duì)象XXXParam
繼承UserInfoParam
,攔截器中只處理@Requestbody
中實(shí)現(xiàn)了UserInfoParam
類的bean
。
如上文controller
中create
方法的入?yún)ⅲ?code>TemplateParam,繼承UserInfoParam
@Data public class TemplateParam extends UserInfoParam{ private Long templateId; // private Long userId; // private String userName; } @Data public class UserInfoParam { // 用戶id private Long userId; // 用戶名稱 private String userName; }
定義Filter實(shí)現(xiàn)類: UserInfoFilter
@Slf4j public class UserInfoFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { CustomHttpServletRequestWrapper customHttpServletRequestWrapper = null; try { HttpServletRequest req = (HttpServletRequest)request; customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(req); }catch (Exception e){ log.warn("customHttpServletRequestWrapper Error:", e); } chain.doFilter((Objects.isNull(customHttpServletRequestWrapper) ? request : customHttpServletRequestWrapper), response); } }
HttpServletRequest
實(shí)現(xiàn)類:CustomHttpServletRequestWrapper
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { // 保存request body的數(shù)據(jù) private String body; // 解析request的inputStream(即body)數(shù)據(jù),轉(zhuǎn)成字符串 public CustomHttpServletRequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } // 賦值給body字段 public void setBody(String body) { this.body = body; } }
HandlerIntercepter
的實(shí)現(xiàn)類:CustomInterceptor
@Slf4j public class CustomInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod)handler; pushUserInfo2Body(request, handlerMethod); return true; } private void pushUserInfo2Body(HttpServletRequest request, HandlerMethod handlerMethod) { try{ String userId = request.getHeader("userId"); String userName = request.getHeader("userName"); MethodParameter[] methodParameters = handlerMethod.getMethodParameters(); if(ArrayUtils.isEmpty(methodParameters)) { return; } for (MethodParameter methodParameter : methodParameters) { Class clazz = methodParameter.getParameterType(); if(ClassUtils.isAssignable(UserInfoParam.class, clazz)){ if(request instanceof CustomHttpServletRequestWrapper){ CustomHttpServletRequestWrapper requestWrapper = (CustomHttpServletRequestWrapper)request; String body = requestWrapper.getBody(); JSONObject param = JSONObject.parseObject(body); param.put("userId", userId); param.put("userName", Objects.isNull(userName) ? null : URLDecoder.decode(userName, "UTF-8")); requestWrapper.setBody(JSON.toJSONString(param)); } } } }catch (Exception e){ log.warn("fill userInfo to request body Error ", e); } }
定義Configuration class
,增加攔截器和過濾器的配置
WebMvcConfigurer
子類:CustomWebMvcConfigurer
@Configuration public class CustomWebMvcConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { CustomInterceptor customInterceptor= new CustomInterceptor(); registry.addInterceptor(customInterceptor); } @Bean public FilterRegistrationBean servletRegistrationBean() { UserInfoFilter userInfoFilter = new UserInfoFilter(); FilterRegistrationBean<UserInfoFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(userInfoFilter); bean.setName("userInfoFilter"); bean.addUrlPatterns("/*"); bean.setOrder(Ordered.LOWEST_PRECEDENCE); return bean; } }
到此,實(shí)現(xiàn)功能的代碼擼完了。啟動(dòng)spring boot App
,就可以curl
訪問restful
接口了
http訪問
curl -X POST \ http://localhost:8080/template/create -H 'Content-Type: application/json' -H 'username: tiankong天空' -H 'userId: 11' -d '{ "templateId": 1000}
效果
可以看到TemplateController.create(…)
打出的信息,userId
和username
的值正是header
中傳的值
toDo
剩下的就看你的了,以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java字符編碼原理(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)
Java開發(fā)中,常常會(huì)遇到亂碼的問題,一旦遇到這種問題,常常比較煩惱,大家都不想承認(rèn)是自己的代碼問題,其實(shí)搞明白編碼的本質(zhì)過程就簡(jiǎn)單多了,接下來小編給大家?guī)韏ava字符編碼原理,要求看看吧2017-04-04Java中RabbitMQ延遲隊(duì)列實(shí)現(xiàn)詳解
這篇文章主要介紹了Java中RabbitMQ延遲隊(duì)列實(shí)現(xiàn)詳解,消息過期后,根據(jù)routing-key的不同,又會(huì)被死信交換機(jī)路由到不同的死信隊(duì)列中,消費(fèi)者只需要監(jiān)聽對(duì)應(yīng)的死信隊(duì)列進(jìn)行消費(fèi)即可,需要的朋友可以參考下2023-09-09springboot整合vue實(shí)現(xiàn)上傳下載文件
這篇文章主要為大家詳細(xì)介紹了springboot整合vue實(shí)現(xiàn)上傳下載文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Java 中 Date 與 Calendar 之間的編輯與轉(zhuǎn)換實(shí)例詳解
這篇文章主要介紹了Java 中 Date 與 Calendar 之間的編輯與轉(zhuǎn)換 ,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07解決springboot使用logback日志出現(xiàn)LOG_PATH_IS_UNDEFINED文件夾的問題
這篇文章主要介紹了解決springboot使用logback日志出現(xiàn)LOG_PATH_IS_UNDEFINED文件夾的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04springboot項(xiàng)目訪問圖片的3種實(shí)現(xiàn)方法(親測(cè)可用)
本文主要介紹了springboot項(xiàng)目訪問圖片的3種實(shí)現(xiàn)方法,通過springboot項(xiàng)目訪問除項(xiàng)目根目錄之外的其它目錄的圖片,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09