分析

  1. 在某些场景下我们希望在Service业务逻辑层获取到当前的HttpServletRequest对象,一个简单直接的处理办法是HttpServletRequest对象通过方法参数传递到下一层,但是这种办法并不灵活,我们需要一种更为通用和灵活的方式。
  2. 对于这种需要在整个线程内使用的对象,我们很容易想到借助于ThreadLocal对象,是的我们可以使用该组件。然后再借助于RequestListener监听器,通过实现该接口在请求进入时将当前的HttpServletRequest添加到特定的ThreadLocal容器中,然后再后面的业务层中就可以直接在当前特定的ThreadLocal容器中获取HttpServletRequest对象。
  3. 上面所描述的功能我们可以通过以下几种方式去实现
    1. 利用ServletRequestListener实现
    2. 利用Filter实现
    3. 利用拦截器实现
  4. 对于上面所描述的功能,需要特别注意的一点是只能在一个线程中去实现该功能。在很多的场景下,在接收到请求之后,我们会通过异步子线程的方式去分担任务处理以此提高处理效率。那么如果在异步子线程中去获取ThreadLocal中的对象又会存在问题了,需要我们特别注意。

    常见的实现方式

    下面几种实现原理都是一样的,使用一个ThreadLocal存储当前HttpServletRequest请求对象,然后后面在service或者dao层直接通过该静态ThreadLocal对象get()获取即可。

    1. 利用ServletRequestListener实现

    ```java public class RequestHolder implements ServletRequestListener {

    //存储HttpServletRequest的线程容器 private static ThreadLocal httpServletRequestHolder =

    1. new ThreadLocal<HttpServletRequest>();

    @Override public void requestInitialized(ServletRequestEvent requestEvent) {

    1. // 绑定到当前线程
    2. HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
    3. httpServletRequestHolder.set(request);

    }

    @Override public void requestDestroyed(ServletRequestEvent requestEvent) {

    1. //移除本次请求对象
    2. httpServletRequestHolder.remove();

    }

    public static HttpServletRequest getHttpServletRequest() {

    1. return httpServletRequestHolder.get();

    }

}

  1. <a name="VxpCg"></a>
  2. ### 2. 利用Filter实现
  3. ```java
  4. public class RequestHolder implements Filter {
  5. //存储HttpServletRequest的线程容器
  6. private static ThreadLocal<HttpServletRequest> httpServletRequestHolder =
  7. new ThreadLocal<HttpServletRequest>();
  8. @Override
  9. public void init(FilterConfig filterConfig) throws ServletException {
  10. }
  11. @Override
  12. public void doFilter(ServletRequest request, ServletResponse response,
  13. FilterChain chain) throws IOException, ServletException {
  14. // 绑定到当前线程
  15. httpServletRequestHolder.set((HttpServletRequest) request);
  16. try {
  17. chain.doFilter(request, response);
  18. } catch (Exception e) {
  19. throw e;
  20. } finally {
  21. //移除本次请求对象
  22. httpServletRequestHolder.remove();
  23. }
  24. }
  25. @Override
  26. public void destroy() {
  27. }
  28. public static HttpServletRequest getHttpServletRequest() {
  29. return httpServletRequestHolder.get();
  30. }
  31. }

3. 利用拦截器实现

  1. public class RequestHolder extends HandlerInterceptorAdapter {
  2. //存储HttpServletRequest的线程容器
  3. private static ThreadLocal<HttpServletRequest> httpServletRequestHolder =
  4. new ThreadLocal<HttpServletRequest>();
  5. @Override
  6. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  7. throws Exception {
  8. // 绑定到当前线程
  9. httpServletRequestHolder.set(request);
  10. return true;
  11. }
  12. @Override
  13. public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
  14. Object handler, Exception ex)
  15. throws Exception {
  16. //移除本次请求对象
  17. httpServletRequestHolder.remove();
  18. }
  19. public static HttpServletRequest getHttpServletRequest() {
  20. return httpServletRequestHolder.get();
  21. }
  22. }

RequestContextHolder原理分析

类似的功能,在SpringMVC中就有开箱即用的实现。代码是:

  1. HttpServletRequest request = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

那么SpringMVC是如何实现的呢?

RequestContextHolder类分析

  1. public abstract class RequestContextHolder {
  2. //存储了当前线程的RequestAttributes对象容器
  3. private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
  4. //存储科可继承父线程的RequestAttributes对象容器
  5. private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
  6. //清理上次线程资源
  7. public static void resetRequestAttributes() {
  8. requestAttributesHolder.remove();
  9. inheritableRequestAttributesHolder.remove();
  10. }
  11. //处理当前请求对象,赋值到容器存储中
  12. public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
  13. if (attributes == null) {
  14. resetRequestAttributes();
  15. } else if (inheritable) {
  16. //是否可继承父线程请求对象
  17. inheritableRequestAttributesHolder.set(attributes);
  18. requestAttributesHolder.remove();
  19. } else {
  20. requestAttributesHolder.set(attributes);
  21. inheritableRequestAttributesHolder.remove();
  22. }
  23. }
  24. //尝试获取当前请求对象属性
  25. @Nullable
  26. public static RequestAttributes getRequestAttributes() {
  27. RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
  28. if (attributes == null) {
  29. attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
  30. }
  31. return attributes;
  32. }
  33. //获取当前请求对象属性
  34. public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
  35. RequestAttributes attributes = getRequestAttributes();
  36. if (attributes == null) {
  37. if (jsfPresent) {
  38. attributes = RequestContextHolder.FacesRequestAttributesFactory.getFacesRequestAttributes();
  39. }
  40. //如果不是HTTP请求则可能抛出以下错误
  41. if (attributes == null) {
  42. 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.");
  43. }
  44. }
  45. return attributes;
  46. }
  47. }

org.springframework.web.servlet.FrameworkServlet#processRequest方法

image.png
processRequest方法在每次请求时都会被调用执行。如上图所示各个请求类型都会去调用该方法。

  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. //获取RequestContextHolder当前请求对象,可能为空
  3. RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
  4. //对HttpServletRequest进行包装,包装成ServletRequestAttributes对象
  5. ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  7. asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
  8. //初始化本次请求对象,这里主要就是更新当前RequestContextHolder存储的线程请求对象
  9. this.initContextHolders(request, localeContext, requestAttributes);
  10. try {
  11. this.doService(request, response);
  12. }
  13. }

org.springframework.web.servlet.FrameworkServlet#initContextHolders方法

调用RequestContextHolder.setRequestAttributes()方法,把requestAttributes对象放入。this.threadContextInheritable默认是false
即把HttpServletRequest的封装对象ServletRequestAttributes与当前线程绑定。

  1. private void initContextHolders(HttpServletRequest request, @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
  2. if (localeContext != null) {
  3. LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
  4. }
  5. if (requestAttributes != null) {
  6. RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
  7. }
  8. }

异步子线程应用,继承父线程请求对象

如果此时我们在Service层方法中添加了@Async注解,进行异步处理。结果如下图所示,无法获取请求对象抛出空指针异常。
image.png

方法一:手动在父线程设置可继承属性,子线程复用父线程请求对象

RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);设置可继承父请求属性。

  1. @Autowired
  2. private MyService myService;
  3. @GetMapping("/get")
  4. public String get(){
  5. RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);
  6. myService.process();
  7. return "OK";
  8. }

方法二:配置线程池

上述方式相对来说需要在每次异步操作时进行手动设置不是很方便,而对于一般的异步处理都是通过线程池分配子线程进行处理的,所以我们也可以通过配置线程池的方式来完成该功能需求。

  1. /**
  2. * 线程处理器,在分配线程时将父线程的RequestContextHolder.currentRequestAttributes();
  3. * 传递给子线程,注意这里的可能会出现一个异常,比如当前请求不是HTTP请求,即分配一个无HTTP参与的任务,
  4. * 比如MQ任务,一般的计算任务等等,但是不会影响任务的执行
  5. * @author zhangyu
  6. * @date 2022/1/27 14:48
  7. **/
  8. public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
  9. @Override
  10. public <T> Future<T> submit(Callable<T> task) {
  11. RequestAttributes requestAttributes=null;
  12. try{
  13. requestAttributes = RequestContextHolder.currentRequestAttributes();
  14. }catch (IllegalStateException e){
  15. }
  16. return super.submit(new ContextAwareCallable(task,requestAttributes ));
  17. }
  18. @Override
  19. public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
  20. RequestAttributes requestAttributes=null;
  21. try{
  22. requestAttributes = RequestContextHolder.currentRequestAttributes();
  23. }catch (IllegalStateException e) {
  24. }
  25. return super.submitListenable(new ContextAwareCallable(task,requestAttributes));
  26. }
  27. }
  1. /**
  2. * 线程处理
  3. * @author zhangyu
  4. * @date 2022/1/27 14:48
  5. **/
  6. public class ContextAwareCallable<T> implements Callable<T> {
  7. private Callable<T> task;
  8. private RequestAttributes context;
  9. public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
  10. this.task = task;
  11. this.context = context;
  12. }
  13. @Override
  14. public T call() throws Exception {
  15. if (context != null) {
  16. RequestContextHolder.setRequestAttributes(context);
  17. }
  18. try {
  19. return task.call();
  20. } finally {
  21. RequestContextHolder.resetRequestAttributes();
  22. }
  23. }
  24. }

配置线程池:

  1. /**
  2. * 线程池配置、启用异步
  3. *
  4. *
  5. */
  6. @EnableAsync(proxyTargetClass = true)
  7. @Configuration
  8. public class AsycTaskExecutorConfig {
  9. @Bean
  10. public TaskExecutor taskExecutor() {
  11. //自定义线程池对象
  12. ThreadPoolTaskExecutor taskExecutor = new ContextAwarePoolExecutor();
  13. taskExecutor.setCorePoolSize(50);
  14. taskExecutor.setMaxPoolSize(100);
  15. return taskExecutor;
  16. }
  17. }

经过测试可以发现可以在子线程中正常获取HTTP请求信息

参考文档

Web应用中Service层获取request对象 | RequestContextHolder的使用
使用@async 注解,导致访问RequestContextHolder获取request为空
RequestContextHolder实践整理