這是JWT
認證條件下的getCurrentLoginUser
代碼實現,請分析性能:java
@Override @ApiOperation("獲取當前登陸的用戶") public User getCurrentLoginUser() { if (this.currentLoginUser != null) { return this.currentLoginUser; } else { // 獲取認證數據並查詢登錄用戶 Claims claims = JwtUtils.getClaims(this.getHttpServletRequest()); Long userId = JwtUtils.getUserId(claims); return this.getAuthInterceptor().getUserById(userId); } }
在生產環境中,currentLoginUser
永遠爲null
,if
不執行。spring
執行else
內的解析JWT
的代碼,解析userId
,再查詢用戶。編程
很明顯,一次請求內,當getCurrentLoginUser
被屢次調用時,會重複解析JWT
,就會產生性能問題。緩存
解決重複解析JWT
的惟一思路就是緩存,第一次解析完userId
並查詢出user
後將這個user
對象緩存。安全
由於併發請求時,每一個請求分配一個線程管理Socket
。數據結構
因此當前待解決的問題就變成了:如何設計一種緩存,使之各線程不影響,線程安全。併發
簡單的設計如上,一個Map
,Thread
做爲key
,用戶緩存做爲value
。ide
ThreadLocal
是啥?去看看《Java
編程的邏輯》吧!post
咱們想用一個相似Map<Thread, User>
這樣的數據結構來設計緩存,其實JDK
中早就爲咱們封裝好了,即ThreadLocal<T>
。性能
栗子
你們來看下面的示例代碼:
ThreadLocal<String> local = new ThreadLocal<>(); Thread thread1 = new Thread(() -> { local.set("test1"); System.out.println("線程1 set 完畢"); try { Thread.sleep(100); } catch (InterruptedException e) { } System.out.println("線程1: " + local.get()); }); Thread thread2 = new Thread(() -> { local.set("test2"); System.out.println("線程2 set 完畢"); try { Thread.sleep(100); } catch (InterruptedException e) { } System.out.println("線程2: " + local.get()); }); thread1.start(); thread2.start();
運行結果以下:
main
線程建立ThreadLocal
對象,thread1
、thread2
操做的是同一個對象local
,thread1
、thread2
分別set
數據,兩線程再次從local
中獲取數據的時候,可以保證二者數據不衝突。
ThreadLocal<String> local = new ThreadLocal<>();
ThreadLocal
中的set
方法實現以下:
獲取當前線程,同時經過線程對象獲取ThreadLocalMap
。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
set
內調用了getMap
方法,看看getMap
的內部實現:
返回線程對象內的threadLocals
屬性。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
Thread
類中的成員屬性threadLocals
,默認爲null
。
接着看:獲取到了Map
以後,用當前的ThreadLocal
對象做爲key
存儲value
進Map
。
if (map != null) map.set(this, value); else createMap(t, value);
這樣設計十分地精妙,每個線程都有獨立的Map
存儲,確定能作到數據隔離且安全。且可方便地建立多個安全的ThreadLocal
進行存儲。
鑑於ThreadLocal
的特性,咱們能夠設計一個SecurityContext
以封裝ThreadLocal<User>
。
@Component public class SecurityContext { private ThreadLocal<User> local = new ThreadLocal<>(); public void set(User user) { local.set(user); } public User get() { return local.get(); } public void clear() { local.remove(); } }
寫一個攔截器,思路以下:
在pre
裏解析JWT
,並存到SecurityContext
裏。
在post
裏clear
,防止線程池線程複用致使數據錯誤。
而後原來的getCurrentLoginUser
方法直接從SecurityContext
中get
便可。
看到SecurityContext
這個名稱是否是很熟悉?
從spring-security
中獲取用戶信息的方法以下,它怎麼實現的呢?
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
點開spring-security
源碼,有三種策略:GlobalSecurityContextHolderStrategy
(即全局線程共享策略)、ThreadLocalSecurityContextHolderStrategy
(本地策略)、InheritableThreadLocalSecurityContextHolderStrategy
(可繼承的本地策略)。
點開源碼後發現,其實spring-security
就是這麼簡單,內部也是用ThreadLocal
實現的,只是此處存儲的信息比較多,使用ThreadLocal<SecurityContext>
。
醉裏且貪歡笑,要愁那得工夫。近來始覺古人書,信著全無是處。 昨夜鬆邊醉倒,問鬆我醉何如。只疑鬆動要來扶,以手推鬆曰去! ——辛棄疾《西江月·遣興》