SpringCloud通用請求字段攔截處理方法
背景
以SpringCloud構(gòu)建的微服務(wù)系統(tǒng)為例,使用前后端分離的架構(gòu),每個(gè)系統(tǒng)都會(huì)提供一些通用的請求參數(shù),例如移動(dòng)端的系統(tǒng)版本信息、IMEI信息,Web端的IP信息,瀏覽器版本信息等,這些參數(shù)可能放在header里,也可以放在參數(shù)里,如果這些參數(shù)需要在每個(gè)方法內(nèi)聲明定義,一來工作量太大,二是這些通用參數(shù)與業(yè)務(wù)接口方法耦合過緊,本身就是一個(gè)不好的設(shè)計(jì)。
這個(gè)問題該如何優(yōu)雅地解決呢?
最佳實(shí)踐
- 利用SpringMVC提供攔截器,對(duì)匹配的請求,抽取通用的header信息(假設(shè)通用字段全部放在header里)
- 將每個(gè)請求的信息單獨(dú)隔離開,互不干擾。
- Controller層使用時(shí),可以將在該請求線程(http線程)里將通用的header信息提取出來使用。
- 請求線程完成時(shí),相應(yīng)的header頭信息對(duì)象需要回收銷毀。
- 實(shí)現(xiàn)方式SpringMVA提供的HandlerInterceptorAdapter可以拿來使用,繼承實(shí)現(xiàn)即可。
- 使用ThreadLocal記錄每個(gè)請求的信息,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è)方法用來處理異步主動(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);
}
}
簡單來說,ThreadLocal最關(guān)鍵的get()和set()方法,都是針對(duì)當(dāng)前線程來操作的,調(diào)用set()方法時(shí)把值放到ThreadMap(Map的一種實(shí)現(xiàn))中,以當(dāng)前線程的hash值為key,get()方法則對(duì)應(yīng)以當(dāng)前線程作為key來取值,從而實(shí)現(xiàn)每個(gè)線程的數(shù)據(jù)是隔離的效果。
另附上ThreadLocal類源碼解讀的導(dǎo)圖,僅供參考

案例實(shí)戰(zhàn)
我們對(duì)實(shí)際業(yè)務(wù)系統(tǒng)進(jìn)行簡化處理,假定header信息固定有ip,uid,deviceId三個(gè)信息,按照上文的實(shí)現(xiàn)思路,開始案例演示。
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;
/**
* 用戶uid
*/
private Long uid;
// 省略getter/setter/構(gòu)造器
}
定義Request請求的封裝類Dto,并引入ThreadLocal:
/**
* 將公共請求頭信息放在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;
}
}
工具類
這里添加一個(gè)簡單的工具類,將HttpServletRequest通過getHeader方法,生成CommonHeader類:
public class HttpUtil {
/**
* 獲取請求頭信息
*
* @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();
}
}
攔截器類實(shí)現(xiàn)
最核心的實(shí)現(xiàn)終于出場了,這里繼承HandlerInterceptorAdapter,這里作了簡化處理:
/**
* 請求頭處理
*
* @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類的接口方法中,如要獲取uid信息,只需要調(diào)用RequestWrap.getUid()方法即可,再也不需要在每個(gè)接口上聲明uid參數(shù)了,如下示例:
/**
* 獲取用戶基礎(chǔ)信息
*/
@PostMapping(value = "/user/info")
public Response<UserInfo> getUserInfo() {
return userManager.getUserInfo(RequestWrap.getUid());
}
總結(jié)
這個(gè)實(shí)戰(zhàn)的目標(biāo)是解決通用header信息的在接口的重復(fù)定義問題,基于HandlerInterceptorAdapter攔截器的實(shí)現(xiàn),ThreadLocal對(duì)線程訪問數(shù)據(jù)的隔離來實(shí)現(xiàn)的,在實(shí)際生產(chǎn)項(xiàng)目應(yīng)用中有很好的借鑒意義,希望對(duì)你有幫助。
到此這篇關(guān)于SpringCloud通用請求字段攔截處理方法的文章就介紹到這了,更多相關(guān)SpringCloud請求字段攔截內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis中updateBatch實(shí)現(xiàn)批量更新
本文主要介紹了Mybatis中updateBatch實(shí)現(xiàn)批量更新,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Java創(chuàng)建型設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory)
當(dāng)系統(tǒng)所提供的工廠所需生產(chǎn)的具體產(chǎn)品并不是一個(gè)簡單的對(duì)象,而是多個(gè)位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中屬于不同類型的具體產(chǎn)品時(shí)需要使用抽象工廠模式,抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態(tài)2022-09-09
Spring Boot詳解各類請求和響應(yīng)的處理方法
平時(shí)只是在用SpringBoot框架,但并沒有詳細(xì)研究過請求和響應(yīng)執(zhí)行的一個(gè)具體過程,所以本文主要來梳理一下SpringBoot請求和響應(yīng)的處理過程2022-07-07
SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理解讀
這篇文章主要介紹了SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
Java簡易學(xué)生成績系統(tǒng)寫法實(shí)例
在本篇文章里小編給大家分享的是關(guān)于Java簡易學(xué)生成績系統(tǒng)寫法實(shí)例以及相關(guān)知識(shí)點(diǎn),有需要的朋友們可以學(xué)習(xí)下。2019-09-09
MyBatis動(dòng)態(tài)SQL foreach標(biāo)簽實(shí)現(xiàn)批量插入的方法示例
這篇文章主要介紹了MyBatis動(dòng)態(tài)SQL foreach標(biāo)簽實(shí)現(xiàn)批量插入的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
SpringBoot實(shí)現(xiàn)單文件與多文件上傳功能
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)單文件與多文件上傳功能,Spring?MVC對(duì)文件上傳做了簡化,而在Spring?Boot中對(duì)此做了更進(jìn)一步的簡化,文件上傳變得更為方便,下面開始演示,需要的小伙伴可以參考一下,希望對(duì)你有所幫助2022-01-01

