(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