这是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>
。
醉里且贪欢笑,要愁那得工夫。近来始觉古人书,信著全无是处。 昨夜松边醉倒,问松我醉何如。只疑松动要来扶,以手推松曰去! ——辛弃疾《西江月·遣兴》