关于HttpServletRequest对象在主线程和线程池传递过程的问题
一,针对一般对象,解决主线程和线程池内线程对象解决方案是用阿里的插件TransmittableThreadLocal
使用案例
(1)将线程池进行TTL包裹
@Bean
public Executor accountThreadPoolTaskExecutor() {
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
corePoolSize = corePoolSize > PROCESSORS ? corePoolSize : PROCESSORS;
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(corePoolSize);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setThreadNamePrefix(VEHICLE_SERVICE);
executor.setRejectedExecutionHandler((r, threadPoolExecutor) -> {
try {
threadPoolExecutor.getQueue().put(r);
} catch (InterruptedException e) {
log.warn(“retry put task error message:{}”, e);
}
});
executor.initialize();
return TtlExecutors.getTtlExecutor(executor.getThreadPoolExecutor());
}
(2)传递对象用TransmittableThreadLocal进行传递
public class RequestPoolThreadContextHolder {
private static final ThreadLocal<ServletRequestAttributes> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
/**
* 设置请求上下文
* @param requestAttributes 请求上下文
*/
public static void setPoolThreadContext(ServletRequestAttributes requestAttributes) {
CONTEXT_HOLDER.set(requestAttributes);
}
/**
* 获取请求上下文
* @return 请求上下文
*/
public static ServletRequestAttributes getPoolThreadContext() {
return CONTEXT_HOLDER.get();
}
/**
* 清除请求上下文
*/
public static void clearPoolThreadContext() {
CONTEXT_HOLDER.remove();
}
}
(3)在需要传递的地方主线程设置共享变量
ServletRequestAttributes att = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//设置主子线程请求上下文
RequestPoolThreadContextHolder.setPoolThreadContext(att);
try {
accountThreadPoolTaskExecutor.execute(() -> {
try {
//将主线程的请求上下文赋值给子线程
RequestContextHolder.setRequestAttributes(RequestPoolThreadContextHolder.getPoolThreadContext(););
//TODO 进行feign请求或者其他操作;
} catch (Exception e) {
System.out.println(“异常信息:” + e);
}
});
}finally {
//清除主子线程请求上下文
RequestPoolThreadContextHolder.clearPoolThreadContext();
}
二,但是针对HttpServletRequest的对象的传递要注意,会因为异步调用后如果主线程的request对象可能因为tomcat先返回导致提前销毁,那么线程池内持有的同一引用因此会被改写,那么也就会导致空指针以及安全问题
(1)先了解Request的生命周期如下
(2)通过上述周期可以理解官方给出了如下警示:(不建议在)
“每个请求对象只有在一个servlet的service方法的范围内有效,或者在一个过滤器的doFilter方法的范围内有效,除非能够异步处理并且请求对象调用startAsync方法。异步处理的时候,请求对象保持有效直到AsyncContext调用了complete方法。容器一般循环利用请求对象,便面创建的请求对象达到最大值。注意在上述描述范围之外调用startAsync方法保持请求对象的引用时不推荐的,因为这样可能有不确定的结果。”
从中可以得到如下信息:
1 三种情况下request有效:service函数内,doFilter函数内,startAsync起的异步线程
2 在三种情况之外,使用request会产生不确定的结果(indeterminate results)
3 大部分容器在实现servlet的时候,为了提高性能,会复用request对象,但这不是规范里必须的
其中提到的startAsync是servlet 3.0开始有的,它是为了让一个工作线程可以在做IO或类似阻塞线程的操作的时候能干其它的事情,但是它要求异步线程都结束了,才会将请求返回给客户端,本质上还是同步的,只是并行了。所以要想异步的处理Request,必须使用servlet自己的异步机制,但是这样并不能满足我们的需求,因为我们就是为了不让主线程等待。
(3)为了避开这点我们可以在塞入ServletRequest对象的时候对HttpServletRequest对象进行深度拷贝并按需重写getHeader方法来解决
public class RequestPoolThreadContextHolder {
/**
*
*/
private static final ThreadLocal<ServletRequestAttributes> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
/**
* 设置请求上下文
*
* @param requestAttributes 请求上下文
*/
public static void setPoolThreadContext(ServletRequestAttributes requestAttributes) {
ServletRequestAttributes request = new ServletRequestAttributes(new MyServletRequest(requestAttributes.getRequest()));
CONTEXT_HOLDER.set(request);
}
/**
* 获取请求上下文
*
* @return 请求上下文
*/
public static ServletRequestAttributes getPoolThreadContext() {
return CONTEXT_HOLDER.get();
}
/**
* 清除请求上下文
*/
public static void clearPoolThreadContext() {
CONTEXT_HOLDER.remove();
}
/**
* 重新构造请求,重写getHeader方法
*/
static class MyServletRequest extends HttpServletRequestWrapper {
private final HashMap<String,String> headMap;
public MyServletRequest(HttpServletRequest request) {
super(request);
headMap = new HashMap();
Enumeration<String> headNameList = request.getHeaderNames();
while (headNameList.hasMoreElements()){
String key = headNameList.nextElement();
headMap.put(key.toLowerCase(),request.getHeader(key));
}
}
@Override
public String getHeader(String name) {
return headMap.get(name.toLowerCase());
}
}
}
———————————–
©著作权归作者所有:来自51CTO博客作者保定大厂螺丝钉的原创作品,谢绝转载,否则将追究法律责任
TransmittableThreadLocal传递ServletRequestAttributes对象在主线程和线程池,避坑指南
https://blog.51cto.com/u_15884608/5870415