目录
保持客户端和服务端会话状态的方式:Cookie+Session
保持客户端和服务端会话状态的方式:Cookie+Session
- HTTP协议是非连接性的,完成操作关闭浏览器后,链接就断开了。所以必须要有一种机制让保证会话的连续性,也就是Session机制
- 当开始一个会话时创建一传Session,并为其赋予一个独一无二的号码sessionID或者其他有用的信息如用户名等等,下一次访问的时候,传递sessionID给后端,就可以识别出属于同一个会话连接
- 通常会将sessionID放在 cookie里面,当允许浏览器使用cookie的时候,cookie会被存储至浏览器中,下一次访问时候会从cookie中取出sessionID然后进行传递
ThreadLocal管理用户信息
![]() | ![]() |
在架构一中,可以看到在每次进行一个功能的时候都需要进行一个用户的校验,耦合度非常高。
架构二中利用了Session的统一管理,每个功能都可以处理自己的业务,可以极大地完成解耦操作。
由session的管理,引入了ThreadLocal
- 每个对APP的请求,都是在一个线程里完成的(包括从发送请求,到请求处理的完成)
- 针对每一个线程,使用ThreadLocal管理当前的用户信息(每个用户的请求都是互相隔离的)
用户第一次发送请求,可通过UserInterceptor进行拦截获取到Request里面的User Info并保存至UserContextHolder这个工具类之中,作为用户信息的上下文(底层通过ThreadLocal实现),然后再去处理其他的业务逻辑。然后Controller,Service和Dao都会去调用UserContextHolder所代表的信息。当有多个用户访问Web APP时,每个请求都有自己的ThreadLocal,都会去使用自己的UserContextHolder然后再去处理各自对应的业务,因此完成了解耦的操作,同时又保证了线程安全。
代码实现
User
@Data
@NoArgsConstructor
public class User {
private String uuid;
private Date lastLogin;
}
定义一个保持用户信息的工具类 UserContextHolder
/*
实现用户信息保存的工具类
*/
public class UserContextHolder {
private static ThreadLocal<User> userInfoHolder = new ThreadLocal<>();
public static void setCurrentUser(User user) {
if(userInfoHolder.get() == null){
userInfoHolder.set(user);//就会和当前的线程进行一一的绑定
}
}
//获得当前的用户
public static User getCurrentUser(){
return userInfoHolder.get();
}
//对ThreadLocal进行清除。因为如果不清除的话,可能会造成内存泄露
public static void removeCurrentUser(){
userInfoHolder.remove();
}
}
建立一个拦截器UserInterceptor-在afterCompletion中需要清除ThreadLocal
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
User user = (User) request.getSession().getAttribute("user");
if(user != null){
UserContextHolder.setCurrentUser(user);
return true;
}
return false;
}
/*
每个请求都是在一个线程里完成的
每一次的请求会产生一个新的Thread吗?
不会,Tomcat会维护一个托管线程池,多次请求间可能会用到一个线程
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserContextHolder.removeCurrentUser();
}
}
UserController中使用UserContextHolder获取保存的用户信息
@RestController
@RequestMapping(value = "/index")
public class UserController {
@RequestMapping(value = "/getOrders", method = RequestMethod.GET)
public String index(){
User user = UserContextHolder.getCurrentUser();
//根据当前用户去处理和其具体有关的逻辑
return "index";
}
}
ThreadLocal在spring框架中的应用
RequestContextHolder这个工具类用来保存和Request上下文相关的信息
public abstract class RequestContextHolder {
private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
public RequestContextHolder() {
}
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}
public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
setRequestAttributes(attributes, false);
}
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
} else if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
} else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
if (attributes == null) {
attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
}
return attributes;
}
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = RequestContextHolder.FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
private static class FacesRequestAttributesFactory {
private FacesRequestAttributesFactory() {
}
@Nullable
public static RequestAttributes getFacesRequestAttributes() {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext != null ? new FacesRequestAttributes(facesContext) : null;
}
}
}
总结
- ThreadLocal是高并发场景下的一大利器
- 特别要注意对象的回收,有可能导致线程间的污染(源于线程间的复用,不同用户操作同一线程,线程保存的还是原来用户的信息)和内存泄露