当前使用版本(必填,否则不予处理)

3.5.2

该问题是如何引起的?(确定最新版也有问题再提!!!)

自定义填充中获取请求上下文

  @Override
  public void insertFill(MetaObject metaObject) {
      // 自定义填充
      System.out.println(ServletUtils.getRequest());
      System.out.println(JSONObject.toJSONString(ServletUtils.getRequest().getHeaderNames()));
      System.out.println(JSONObject.toJSONString(ServletUtils.getRequest().getParameterNames()));
      this.strictInsertFill(metaObject, tenantSysField, String.class, ServletUtils.getRequest().getHeader(tenantSysField));
  }

异步线程装饰器:

  @Override
  public Runnable decorate(Runnable runnable) {
      ServletRequestAttributes context = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
      return () -> {
          try {
              RequestContextHolder.setRequestAttributes(context);
              runnable.run();
          } finally {
              RequestContextHolder.resetRequestAttributes();
          }
      };
  }

报错信息

这两个不是null,但是空的[]

System.out.println(JSONObject.toJSONString(ServletUtils.getRequest().getHeaderNames()));
System.out.println(JSONObject.toJSONString(ServletUtils.getRequest().getParameterNames()));

Comment From: miemieYaho

那就是你代码问题,线程之间没同步数据

Comment From: yl-yue

@miemieYaho 抱歉,问题已定位,不是mybatis-puls清空了上下文,而是forest这个httpclient清空了上下文

异步线程上下文这个同步了的,上面提到的异步线程装饰器就是用来同步上下文的,只是上下文在异步方法中执行的forest请求时被清空了,而mybatis-puls的代码在forest后执行。

public interface TaskDecorator {

    /**
     * Decorate the given {@code Runnable}, returning a potentially wrapped
     * {@code Runnable} for actual execution, internally delegating to the
     * original {@link Runnable#run()} implementation.
     * @param runnable the original {@code Runnable}
     * @return the decorated {@code Runnable}
     */
    Runnable decorate(Runnable runnable);

}

Comment From: yl-yue

抱歉,这个问题是Servlet已关闭的原因(调用 destroy() 方法),导致子线程获取的上下文为空。很多年前我已解决过此问题,并做了规范封装,但现在又忘了。。。在这记录下,目前采用MDC传递上下文,不用Servlet了,也不需要去阻塞Servlet.

贴一下以前的封装代码:

@AllArgsConstructor
public abstract class AbstractContextDecorator implements TaskDecorator {

    protected AsyncProperties asyncProperties;

    /**
     * 启用 ServletAsyncContext,异步上下文最长生命周期(最大阻塞父线程多久)
     * <p>用于阻塞父线程 Servlet 的关闭(调用 destroy() 方法),导致子线程获取的上下文为空</p>
     *
     * @param context         父线程上下文
     * @param asyncProperties 异步属性配置
     */
    protected void enableServletAsyncContext(ServletRequestAttributes context, AsyncProperties asyncProperties) {
        if (!asyncProperties.isEnableServletAsyncContext()) {
            return;
        }

        HttpServletRequest request = context.getRequest();
        request.startAsync();
        Object servletAsyncContextTimeoutMillis = request.getAttribute(AsyncProperties.SERVLET_ASYNC_CONTEXT_TIMEOUT_MILLIS);
        if (servletAsyncContextTimeoutMillis == null) {
            servletAsyncContextTimeoutMillis = asyncProperties.getServletAsyncContextTimeoutMillis();
        }

        request.getAsyncContext().setTimeout(Convert.toLong(servletAsyncContextTimeoutMillis));
    }

    /**
     * 完成异步请求处理并关闭响应流
     *
     * @param context         父线程上下文
     * @param asyncProperties 异步属性配置
     */
    protected void completeServletAsyncContext(ServletRequestAttributes context, AsyncProperties asyncProperties) {
        if (asyncProperties.isEnableServletAsyncContext()) {
            context.getRequest().getAsyncContext().complete();
        }
    }

}

Comment From: morning-reading

我也是遇到这样问题,楼主这解决方案可以,能提供下代码吗?