背景
以SpringCloud構建的微服務系統為例,使用前后端分離的架構,每個系統都會提供一些通用的請求參數,例如移動端的系統版本信息、IMEI信息,Web端的IP信息,瀏覽器版本信息等,這些參數可能放在header里,也可以放在參數里,如果這些參數需要在每個方法內聲明定義,一來工作量太大,二是這些通用參數與業務接口方法耦合過緊,本身就是一個不好的設計。
這個問題該如何優雅地解決呢?
最佳實踐
- 利用SpringMVC提供攔截器,對匹配的請求,抽取通用的header信息(假設通用字段全部放在header里)
- 將每個請求的信息單獨隔離開,互不干擾。
- Controller層使用時,可以將在該請求線程(http線程)里將通用的header信息提取出來使用。
- 請求線程完成時,相應的header頭信息對象需要回收銷毀。
- 實現方式SpringMVA提供的HandlerInterceptorAdapter可以拿來使用,繼承實現即可。
- 使用ThreadLocal記錄每個請求的信息,ThreadLocal有隔離線程變量的作用。
HandlerInterceptorAdapter的源碼實現及注釋
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 在業務接口方法處理之前被調用,可以在這里對通用的header信息進行提取 return true ; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { // 這個方法在業務接口方法執行完成后,生成SpringMVC ModelAndView之前被調用 // 今天這個案例我們不用此方法,故可以不實現。 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { // 這個方法在DispatcherServlet完全處理完成后被調用,可以在這里對ThreadLocal的內容進行釋放 } @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 這個方法用來處理異步主動,但也會先行調用preHandle,然后執行此方法,異步線程完成后會執行postHandle和afterCompletion兩方法,這里暫時用不上。 } } |
ThreadLocal的源碼主要實現及注釋
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
public class ThreadLocal<T> { protected T initialValue() { return null ; } public T get() { // 獲取當前的線程 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) { // 獲取當前的線程 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最關鍵的get()和set()方法,都是針對當前線程來操作的,調用set()方法時把值放到ThreadMap(Map的一種實現)中,以當前線程的hash值為key,get()方法則對應以當前線程作為key來取值,從而實現每個線程的數據是隔離的效果。
另附上ThreadLocal類源碼解讀的導圖,僅供參考
案例實戰
我們對實際業務系統進行簡化處理,假定header信息固定有ip,uid,deviceId三個信息,按照上文的實現思路,開始案例演示。
DTO定義
通用的header信息,使用Dto對象進行封裝:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Data public class CommonHeader implements Serializable { private static final long serialVersionUID = -3949488282201167943L; /** * 真實ip */ private String ip; /** * 設備id */ private String deviceId; /** * 用戶uid */ private Long uid; // 省略getter/setter/構造器 } |
定義Request請求的封裝類Dto,并引入ThreadLocal:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
/** * 將公共請求頭信息放在ThreadLocal中去 */ public class RequestWrap { private static ThreadLocal<CommonHeader> current = new ThreadLocal<>(); /** * 獲取靜態的ThreadLocal對象 * @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(); } /** * 獲取封裝對象 * @return */ public static CommonHeader getCommonReq() { CommonHeader request = current.get(); if (request == null ) { return new CommonHeader(StringUtils.EMPTY, StringUtils.EMPTY,0L); } return request; } } |
工具類
這里添加一個簡單的工具類,將HttpServletRequest通過getHeader方法,生成CommonHeader類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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(); } } |
攔截器類實現
最核心的實現終于出場了,這里繼承HandlerInterceptorAdapter,這里作了簡化處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/** * 請求頭處理 * * @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 { } } |
如上一章節描述的邏輯,在preHandle方法內將request中的ip,uid,deviceId封裝到RequestWrap對象里,在afterCompletion中對該線程的ThreadLocal值進行釋放。
業務接口方法的使用
在Controller類的接口方法中,如要獲取uid信息,只需要調用RequestWrap.getUid()方法即可,再也不需要在每個接口上聲明uid參數了,如下示例:
1
2
3
4
5
6
7
|
/** * 獲取用戶基礎信息 */ @PostMapping (value = "/user/info" ) public Response<UserInfo> getUserInfo() { return userManager.getUserInfo(RequestWrap.getUid()); } |
總結
這個實戰的目標是解決通用header信息的在接口的重復定義問題,基于HandlerInterceptorAdapter攔截器的實現,ThreadLocal對線程訪問數據的隔離來實現的,在實際生產項目應用中有很好的借鑒意義,希望對你有幫助。
到此這篇關于SpringCloud通用請求字段攔截處理方法的文章就介紹到這了,更多相關SpringCloud請求字段攔截內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/huangying2124/p/13264753.html