SpringCloud通用請(qǐng)求字段攔截處理方法
背景
以SpringCloud構(gòu)建的微服務(wù)系統(tǒng)為例,使用前后端分離的架構(gòu),每個(gè)系統(tǒng)都會(huì)提供一些通用的請(qǐng)求參數(shù),例如移動(dòng)端的系統(tǒng)版本信息、IMEI信息,Web端的IP信息,瀏覽器版本信息等,這些參數(shù)可能放在header里,也可以放在參數(shù)里,如果這些參數(shù)需要在每個(gè)方法內(nèi)聲明定義,一來(lái)工作量太大,二是這些通用參數(shù)與業(yè)務(wù)接口方法耦合過(guò)緊,本身就是一個(gè)不好的設(shè)計(jì)。
這個(gè)問(wèn)題該如何優(yōu)雅地解決呢?
最佳實(shí)踐
- 利用SpringMVC提供攔截器,對(duì)匹配的請(qǐng)求,抽取通用的header信息(假設(shè)通用字段全部放在header里)
- 將每個(gè)請(qǐng)求的信息單獨(dú)隔離開(kāi),互不干擾。
- Controller層使用時(shí),可以將在該請(qǐng)求線程(http線程)里將通用的header信息提取出來(lái)使用。
- 請(qǐng)求線程完成時(shí),相應(yīng)的header頭信息對(duì)象需要回收銷(xiāo)毀。
- 實(shí)現(xiàn)方式SpringMVA提供的HandlerInterceptorAdapter可以拿來(lái)使用,繼承實(shí)現(xiàn)即可。
- 使用ThreadLocal記錄每個(gè)請(qǐng)求的信息,ThreadLocal有隔離線程變量的作用。
HandlerInterceptorAdapter的源碼實(shí)現(xiàn)及注釋
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 在業(yè)務(wù)接口方法處理之前被調(diào)用,可以在這里對(duì)通用的header信息進(jìn)行提取 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { // 這個(gè)方法在業(yè)務(wù)接口方法執(zhí)行完成后,生成SpringMVC ModelAndView之前被調(diào)用 // 今天這個(gè)案例我們不用此方法,故可以不實(shí)現(xiàn)。 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { // 這個(gè)方法在DispatcherServlet完全處理完成后被調(diào)用,可以在這里對(duì)ThreadLocal的內(nèi)容進(jìn)行釋放 } @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 這個(gè)方法用來(lái)處理異步主動(dòng),但也會(huì)先行調(diào)用preHandle,然后執(zhí)行此方法,異步線程完成后會(huì)執(zhí)行postHandle和afterCompletion兩方法,這里暫時(shí)用不上。 } }
ThreadLocal的源碼主要實(shí)現(xiàn)及注釋
public class ThreadLocal<T> { protected T initialValue() { return null; } public T get() { // 獲取當(dāng)前的線程 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } public void set(T value) { // 獲取當(dāng)前的線程 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } }
簡(jiǎn)單來(lái)說(shuō),ThreadLocal最關(guān)鍵的get()和set()方法,都是針對(duì)當(dāng)前線程來(lái)操作的,調(diào)用set()方法時(shí)把值放到ThreadMap(Map的一種實(shí)現(xiàn))中,以當(dāng)前線程的hash值為key,get()方法則對(duì)應(yīng)以當(dāng)前線程作為key來(lái)取值,從而實(shí)現(xiàn)每個(gè)線程的數(shù)據(jù)是隔離的效果。
另附上ThreadLocal類(lèi)源碼解讀的導(dǎo)圖,僅供參考
案例實(shí)戰(zhàn)
我們對(duì)實(shí)際業(yè)務(wù)系統(tǒng)進(jìn)行簡(jiǎn)化處理,假定header信息固定有ip,uid,deviceId三個(gè)信息,按照上文的實(shí)現(xiàn)思路,開(kāi)始案例演示。
DTO定義
通用的header信息,使用Dto對(duì)象進(jìn)行封裝:
@Data public class CommonHeader implements Serializable { private static final long serialVersionUID = -3949488282201167943L; /** * 真實(shí)ip */ private String ip; /** * 設(shè)備id */ private String deviceId; /** * 用戶(hù)uid */ private Long uid; // 省略getter/setter/構(gòu)造器 }
定義Request請(qǐng)求的封裝類(lèi)Dto,并引入ThreadLocal:
/** * 將公共請(qǐng)求頭信息放在ThreadLocal中去 */ public class RequestWrap { private static ThreadLocal<CommonHeader> current = new ThreadLocal<>(); /** * 獲取靜態(tài)的ThreadLocal對(duì)象 * @return */ public static ThreadLocal<CommonHeader> getCurrent() { return current; } /** * 獲取ip * @return */ public static String getIp() { CommonHeader request = current.get(); if (request == null) { return StringUtils.EMPTY; } return request.getIp(); } /** * 獲取uid * @return */ public static Long getUid() { CommonHeader request = current.get(); if (request == null) { return null; } return request.getUid(); } /** * 獲取封裝對(duì)象 * @return */ public static CommonHeader getCommonReq() { CommonHeader request = current.get(); if (request == null) { return new CommonHeader(StringUtils.EMPTY, StringUtils.EMPTY,0L); } return request; } }
工具類(lèi)
這里添加一個(gè)簡(jiǎn)單的工具類(lèi),將HttpServletRequest通過(guò)getHeader方法,生成CommonHeader類(lèi):
public class HttpUtil { /** * 獲取請(qǐng)求頭信息 * * @param request * @return */ public static CommonHeader getCommonHeader(HttpServletRequest request) { String UID = request.getHeader("uid"); Long uid = null; if (StringUtils.isNotBlank(UID)) { uid = Long.parseLong(UID); } return new CommonHeader(HttpUtil.getIp(request), request.getHeader("deviceId"), uid); } /** * 獲取IP * * @param request * @return */ public static String getIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (null != ip && !"".equals(ip.trim()) && !"unknown".equalsIgnoreCase(ip)) { int index = ip.indexOf(','); if (index != -1) { return ip.substring(0, index); } else { return ip; } } ip = request.getHeader("X-Real-IP"); if (null != ip && !"".equals(ip.trim()) && !"unknown".equalsIgnoreCase(ip)) { return ip; } return request.getRemoteAddr(); } }
攔截器類(lèi)實(shí)現(xiàn)
最核心的實(shí)現(xiàn)終于出場(chǎng)了,這里繼承HandlerInterceptorAdapter,這里作了簡(jiǎn)化處理:
/** * 請(qǐng)求頭處理 * * @author yangfei */ @Component public class BaseInterceptor extends HandlerInterceptorAdapter { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BaseInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { RequestWrap.getThreadLocal().set(HttpUtil.getCommonHeader(request)); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { RequestWrap.getThreadLocal().remove(); } @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
如上一章節(jié)描述的邏輯,在preHandle方法內(nèi)將request中的ip,uid,deviceId封裝到RequestWrap對(duì)象里,在afterCompletion中對(duì)該線程的ThreadLocal值進(jìn)行釋放。
業(yè)務(wù)接口方法的使用
在Controller類(lèi)的接口方法中,如要獲取uid信息,只需要調(diào)用RequestWrap.getUid()方法即可,再也不需要在每個(gè)接口上聲明uid參數(shù)了,如下示例:
/** * 獲取用戶(hù)基礎(chǔ)信息 */ @PostMapping(value = "/user/info") public Response<UserInfo> getUserInfo() { return userManager.getUserInfo(RequestWrap.getUid()); }
總結(jié)
這個(gè)實(shí)戰(zhàn)的目標(biāo)是解決通用header信息的在接口的重復(fù)定義問(wèn)題,基于HandlerInterceptorAdapter攔截器的實(shí)現(xiàn),ThreadLocal對(duì)線程訪問(wèn)數(shù)據(jù)的隔離來(lái)實(shí)現(xiàn)的,在實(shí)際生產(chǎn)項(xiàng)目應(yīng)用中有很好的借鑒意義,希望對(duì)你有幫助。
到此這篇關(guān)于SpringCloud通用請(qǐng)求字段攔截處理方法的文章就介紹到這了,更多相關(guān)SpringCloud請(qǐng)求字段攔截內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決java啟動(dòng)時(shí)報(bào)線程占用報(bào)錯(cuò):Exception?in?thread?“Thread-14“?java.ne
這篇文章主要給大家介紹了關(guān)于解決java啟動(dòng)時(shí)報(bào)線程占用:Exception?in?thread?“Thread-14“?java.net.BindException:?Address?already?in?use:?bind的相關(guān)資料,文中將解決的辦法介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04SpringBoot中的Redis?緩存問(wèn)題及操作方法
這篇文章主要介紹了SpringBoot中的Redis?緩存,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10JVM對(duì)象創(chuàng)建和內(nèi)存分配原理解析
這篇文章主要介紹了JVM對(duì)象創(chuàng)建和內(nèi)存分配原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Gradle修改本地倉(cāng)庫(kù)的位置方法實(shí)現(xiàn)
這篇文章主要介紹了Gradle修改本地倉(cāng)庫(kù)的位置方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07SpringBoot將多個(gè)文件夾進(jìn)行壓縮的兩種方法(瀏覽器下載和另存為)
Spring Boot項(xiàng)目通常不會(huì)自動(dòng)對(duì)文件夾進(jìn)行壓縮,不過(guò),在打包應(yīng)用時(shí),如果你使用了Maven或Gradle這樣的構(gòu)建工具,并且配置了相應(yīng)的插件,可以在打成jar或war包的時(shí)候?qū)⒁蕾?lài)的庫(kù)文件合并并壓縮,本文介紹了SpringBoot將多個(gè)文件夾進(jìn)行壓縮的兩種方法2024-07-07java 實(shí)現(xiàn)比較版本號(hào)功能
本篇文章主要介紹了java 中涉及到客戶(hù)端的系統(tǒng)經(jīng)常需要用到比較版本號(hào)的功能,并附小示例,希望能幫助需要的小伙伴2016-07-07