RequestContextHolder practice collation

Posted by phpflixnewbie on Wed, 02 Feb 2022 16:49:37 +0100

(1) Analysis

[1] In some scenarios, we want to obtain the current HttpServletRequest object in the Service business logic layer. A simple and direct processing method is that the HttpServletRequest object is passed to the next layer through method parameters, but this method is not flexible. We need a more general and flexible method.

[2] For this kind of object that needs to be used in the whole thread, we can easily think of using ThreadLocal object. Yes, we can use this component. Then, with the help of the RequestListener listener, by implementing this interface, the current HttpServletRequest is added to the specific ThreadLocal container when the request enters, and then the following business layer can directly obtain the HttpServletRequest object in the current specific ThreadLocal container.

[3] The functions described above can be realized in the following ways
Using ServletRequestListener to realize
Using Filter to realize
Using interceptor

[4] For the functions described above, it should be noted that this function can only be implemented in one thread. In many scenarios, after receiving the request, we will share the task processing through asynchronous sub threads, so as to improve the processing efficiency. Then there will be a problem if we get the object in ThreadLocal in the different step sub thread, which needs our special attention.

(2) Common implementation methods

The following implementation principles are the same. Use a ThreadLocal to store the current HttpServletRequest request request object, and then directly obtain it through the static ThreadLocal object get() in the service or dao layer.

[1] Using ServletRequestListener to realize

public class RequestHolder implements ServletRequestListener {

    //Thread container that stores HttpServletRequest
    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();
    
    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
		// Bind to current thread
        HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
        httpServletRequestHolder.set(request); 
    }
    
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
		//Remove this request object
        httpServletRequestHolder.remove(); 
    }
    
    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

[2] Using Filter to realize

public class RequestHolder implements Filter {
    //Thread container that stores HttpServletRequest 
    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
			// Bind to current thread
        httpServletRequestHolder.set((HttpServletRequest) request); 
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
			//Remove this request object
            httpServletRequestHolder.remove(); 
        }
    }

    @Override
    public void destroy() {
    }

    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

[3] Using interceptor

public class RequestHolder extends HandlerInterceptorAdapter {
    //Thread container that stores HttpServletRequest
    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
        throws Exception {
		 // Bind to current thread
        httpServletRequestHolder.set(request);
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
            Object handler, Exception ex) 
        throws Exception {
		//Remove this request object
        httpServletRequestHolder.remove();
    }
    
    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

(3) Principle analysis of RequestContextHolder

[1] RequestContextHolder class analysis

public abstract class RequestContextHolder {
    //The container that stores the RequestAttributes object of the current thread
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
	//Inheritable attributes of the parent thread
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");

    //Clean up the last thread resource
    public static void resetRequestAttributes() {
        requestAttributesHolder.remove();
        inheritableRequestAttributesHolder.remove();
    }
     
	//Process the current request object and assign it to the container storage
    public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        } else if (inheritable) {
		    //Can the parent thread request object be inherited
            inheritableRequestAttributesHolder.set(attributes);
            requestAttributesHolder.remove();
        } else {
            requestAttributesHolder.set(attributes);
            inheritableRequestAttributesHolder.remove();
        }

    }
    //Trying to get the properties of the current request object
    @Nullable
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
        if (attributes == null) {
            attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
        }

        return attributes;
    }
    //Get the properties of the current request object
    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = RequestContextHolder.FacesRequestAttributesFactory.getFacesRequestAttributes();
            }
			//If it is not an HTTP request, the following error may be thrown
            if (attributes == null) {
                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.");
            }
        }

        return attributes;
    }
}

[2]org.springframework.web.servlet.FrameworkServlet#processRequest core processing class analysis.

The processRequest method is called and executed every time a request is made. As shown in the figure above, each request type will call this method.

   protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //Get the current request object of RequestContextHolder, which may be empty
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		//Wrap HttpServletRequest into ServletRequestAttributes object
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
		//Initialize this request object, which is mainly to update the thread request object stored in the current RequestContextHolder
        this.initContextHolders(request, localeContext, requestAttributes);

        try {
            this.doService(request, response);
        }

    }

[3]org.springframework.web.servlet.FrameworkServlet#initContextHolders. Call requestcontextholder Setrequestattributes() method, put the requestAttributes object into the. this.threadContextInheritable is false by default.
That is, bind the encapsulated object ServletRequestAttributes of HttpServletRequest to the current thread.

  private void initContextHolders(HttpServletRequest request, @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }

        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }

    }

(4) Asynchronous sub thread application inherits the request object of the parent thread

If we add @ Async annotation to the Service layer method for asynchronous processing. The result is as shown in the following figure. The request object cannot be obtained and a null pointer exception is thrown

[1] Method 1: manually set the inheritable property in the parent thread, and the child thread reuses the request object of the parent thread. RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);
Set inheritable parent request properties.

   @Autowired
   private MyService myService;
   
    @GetMapping("/get")
    public String  get(){
	    
        RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);
        myService.process();
        return  "OK";
    }

[2] The above methods are relatively inconvenient for manual setting during each asynchronous operation. For general asynchronous processing, sub threads are allocated through the thread pool, so we can also configure the thread pool to meet the functional requirements.

/**
 *  Thread processor, when allocating a thread, the requestcontextholder of the parent thread currentRequestAttributes();
 * Pass it to the child thread. Note that there may be an exception here. For example, the current request is not an HTTP request, that is, assign a task without HTTP participation,
 * For example, MQ tasks and general computing tasks will not affect the execution of tasks
 * @author zhangyu
 * @date 2022/1/27  14:48
 **/
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        RequestAttributes requestAttributes=null;
        try{
             requestAttributes = RequestContextHolder.currentRequestAttributes();
        }catch (IllegalStateException e){
          
        }
        return super.submit(new ContextAwareCallable(task,requestAttributes ));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        RequestAttributes requestAttributes=null;
        try{
            requestAttributes = RequestContextHolder.currentRequestAttributes();
        }catch (IllegalStateException e) {
          
        }
        return super.submitListenable(new ContextAwareCallable(task,requestAttributes));
    }
}
/**
 *   Thread processing
 * @author zhangyu
 * @date 2022/1/27  14:48
 **/
public class ContextAwareCallable<T> implements Callable<T> {
    private Callable<T> task;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
        this.task = task;
        this.context = context;
    }

    @Override
    public T call() throws Exception {
        if (context != null) {
            RequestContextHolder.setRequestAttributes(context);
        }

        try {
            return task.call();
        } finally {
            RequestContextHolder.resetRequestAttributes();
        }
    }
}

Configure thread pool

/**
 * Thread pool configuration, enable asynchronous
 * 
 *
 */
@EnableAsync(proxyTargetClass = true)
@Configuration
public class AsycTaskExecutorConfig {

	@Bean
	public TaskExecutor taskExecutor() {
		//Custom thread pool object
		ThreadPoolTaskExecutor taskExecutor = new ContextAwarePoolExecutor();
		taskExecutor.setCorePoolSize(50);
		taskExecutor.setMaxPoolSize(100);
		return taskExecutor;
	}

}


After testing, it can be found that the HTTP request information can be obtained normally in the sub thread

Topics: Java Back-end