定义请求头
在实际开发过程中,一般把用户的一些基本信息放到http请求头,所以我们先定义一个model来保存请求头中的信息
public class UserHeader {
private String userId;
private String version;
}
定义threadlocal
ThreadLocal定义为static类型
public class UserHeaderHodler {
public static ThreadLocal<UserHeader> threadLocal = new ThreadLocal<>();
/**
* 需要兼容userHeader为null的情况
* @return
*/
public static String getUserId() {
return Optional.ofNullable(threadLocal.get())
.map(userHeader -> userHeader.getUserId())
.orElse("");
}
public static String getVersion() {
return Optional.ofNullable(threadLocal.get())
.map(userHeader -> userHeader.getVersion())
.orElse("");
}
}
定义拦截器
定义一个拦截器,拦截每次http请求,获取到http请求头的内容,保存到threadlocal,方便具体业务逻辑中使用这些信息
public class UserHeaderIntercepter implements HandlerInterceptor {
/**
* 一次http方法调用都是在一个线程完成的
* 因为本次请求的很多地方都可能会用到userId
* 所以放在threadLocal可以减少参数的传递
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserHeader userHeader = UserHeader.builder()
.userId(request.getHeader("userId"))
.version(request.getHeader("version"))
.build();
UserHeaderHodler.threadLocal.set(userHeader);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/**
* 本次调用结束了,就可以释放threadLocal资源避免内存泄漏的情况发生
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHeaderHodler.threadLocal.remove();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
在拦截器的afterCompletion的方法中执行了remove方法,为了避免线程执行结束但是没有销毁,比如线程属于线程池的情况,这样线程一直持有对象,如果对象很大,就会造成内存泄漏,从而导致内存溢出
threadlocal保存的信息使用
获取基本用户请求头信息属于一个可以共用的逻辑,在许多moduel都会用到,因此我们把它封装到一个通过moduel里,其他项目通过dependency的方式使用
看一下controller这个moduel中的service方法是如何使用拦截器中保存的http请求头信息的
@Service
public class Service1 {
/**
* 拦截器和service方法同处于一个线程,所以可以通过这种方式,保证userHeader的线程安全
* 同时避免参数多次传递
* @return
*/
public String m1() {
return UserHeaderHodler.getUserId() + "====>" + UserHeaderHodler.getVersion();
}
}
最终达到了使用threadlocal的两个目的:
- 线程安全,每次请求都在一个线程中完成,恰好threadlocal保存的信息属于每个线程所独有,threadlocal保存的信息其实是和每个线程绑定的,完全避免了变量共享带来的线程安全问题
- 减少参数传递,本次请求的任何方法不管是controller还是service层,又或者是dao层都可以使用threadlocal来获取用户相关的信息
运行结果
idea有个方便好用的工具:reatfulTool,我们使用他来验证代码效果
先设置请求头
点击send按钮之后看到了我们想要的效果
最终实现了通过拦截器获取http请求头信息,保存到threadlocal,提供给具体的业务逻辑使用
拦截器的定义和加载也有一些知识点需要学习,可以加深对springboot框架的理解,在下一篇文章中,我们会继续分析拦截器的定义