[ SpringBoot ] 对开启 debug 模式后放在 Threadlocal 对象中 HttpServletRequest#getInputStream() 无法获取的疑问
各路大神,感谢花时间来一起讨论。我们的业务场景如下:
代码如下:
服务收到调用,先走 Filter , 并且拿到 request, 放入 Threadlocal 中,因为是线程池之间的传递所以使用了阿里的 ttl 进行全局的 Request 传递 当前线程: tomcat 线程
/** Servlet 属性全局传递 ThreadLocal 前主要用于未来的分布式跟踪,以及线程池之间属性传递 */
public static final ThreadLocal<HttpServletRequest> GLOBAL_SERVLET_REQUEST =
new TransmittableThreadLocal<>();
@Override
protected void doFilterInternal(
HttpServletRequest request,
@Nullable HttpServletResponse response,
@Nullable FilterChain filterChain)
throws ServletException, IOException {
// 先清除 threadLocal 类中的变量
GLOBAL_SERVLET_REQUEST.remove();
// 重新放入 request 对象
GLOBAL_SERVLET_REQUEST.set(request);
//传递至下一个链中
Objects.requireNonNull(filterChain).doFilter(request, response);
}
使用 Spring 的 Aop + 注解形式 去拦截 controller 并异步调用请求日志的落库,这时候进行了线程池隔离。日志专用线程池落库 当前线程: tomcat 线程
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
// 调用异步写入日志
systemLogService.executeSaveLog(joinPoint, null, jsonResult);
}
因为这是一个独立的的线程池,也就是一个新的线程在处理这些。所以我必须把 request 传递进来我才可以获取到相关 request 的信息
我们都知道在 http 的 body 中,被 Java EE 的规范封装在了 HttpServletRequest 父类的 getInputStream()方法中,所以我们可以从这里获取到 body 中相关的内容
@ToString
public class LocalServletUtils extends AbstractServletUtils {
/**
* 从全局的 threadLocal 中获取
*
* @return HttpServletRequest
*/
@Override
public HttpServletRequest getRequest() {
return GlobalRequestContextFilter.GLOBAL_SERVLET_REQUEST.get();
}
/**
* 从 request 中获取 body
* 使用了模板方法模式,方便预览直接粘贴在此处了
* @return HttpServletRequest
*/
public String getBody() {
try {
BufferedReader reader =
new BufferedReader(new InputStreamReader(getRequest().getInputStream()));
//https://github.com/dromara/hutool/blob/0d8dfb73d87c28d2633a7826cc9a16f8a476372d/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java#L423
return IoUtil.read(reader);
} catch (Exception e) {
return "get body error";
}
}
}
hutool io IoUtil code:
/**
* 从 Reader 中读取 String ,读取完毕后并不关闭 Reader
*
* @param reader Reader
* @return String
* @throws IORuntimeException IO 异常
*/
public static String read(Reader reader) throws IORuntimeException {
final StringBuilder builder = StrUtil.builder();
final CharBuffer buffer = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
try {
while (-1 != reader.read(buffer)) {
builder.append(buffer.flip().toString());
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
return builder.toString();
}
日志的落库使用了 @Async 结合日志专用线程池去处理日志的落库 当前线程: 日志线程
@Async(AsyncConfiguration.LOG_EXECUTOR)
public void executeSaveLog(JoinPoint joinPoint, Exception e, Object json) {
// 从 ThreadLocal 中获取 ServletUtils 工具类实例,用于获取 request 中的数据
AbstractServletUtils servletUtils = new LocalServletUtils();
//具体的业务代码,在这里获取 body,就在这里 request 对象忽悠
servletUtils.getBody();
}
但是以上代码,有几种情况
始终没搞明白这是为什么。。。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.