問題
朋友遇到一個問題:他想在Service方法中使用HttpServletRequest的API,但是又不想把HttpServletRequest對象當作這個Service方法的參數傳過來,原因是這個方法被N多Controller調用,加一個參數就得改一堆代碼。一句話:就是他懶。不過,這個問題該這么解決呢?
思考
不把HttpServletRequest當作參數傳過來,這意味着要在Service的方法中直接獲取到HttpServletRequest對象。
我們知道,一次請求,Web應用服務器就會分配一個線程去處理。也就是說,在Service方法中獲取到的HttpServletRequest對象需要滿足:線程內共享,線程間隔離。
這恰恰是ThreadLocal的應用場景。
思路
那么,就需要在請求執行之前獲取到HttpServletRequest,把它set()到某個類的ThreadLocal類型的靜態成員中,使用的時候直接通過靜態方式訪問到這個ThreadLocal對象,調用它的get()方法,即可獲取到線程隔離的HttpServletRequest了。最后,在請求結束后,要調用ThreadLocal的remove()方法,清理資源引用。
實現
方式一 利用ServletRequestListener實現
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; public class RequestHolder implements ServletRequestListener { private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = new ThreadLocal<HttpServletRequest>(); @Override public void requestInitialized(ServletRequestEvent requestEvent) { HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest(); httpServletRequestHolder.set(request); // 綁定到當前線程 } @Override public void requestDestroyed(ServletRequestEvent requestEvent) { httpServletRequestHolder.remove(); // 清理資源引用 } public static HttpServletRequest getHttpServletRequest() { return httpServletRequestHolder.get(); } }
方式二 利用Filter實現
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; public class RequestHolder implements Filter { 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 { httpServletRequestHolder.set((HttpServletRequest) request); // 綁定到當前線程 try { chain.doFilter(request, response); } catch (Exception e) { throw e; } finally { httpServletRequestHolder.remove(); // 清理資源引用 } } @Override public void destroy() { } public static HttpServletRequest getHttpServletRequest() { return httpServletRequestHolder.get(); } }
方式三 利用SpringMVC的攔截器實現
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class RequestHolder extends HandlerInterceptorAdapter { private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = new ThreadLocal<HttpServletRequest>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { httpServletRequestHolder.set(request); // 綁定到當前線程 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { httpServletRequestHolder.remove(); // 清理資源引用 } public static HttpServletRequest getHttpServletRequest() { return httpServletRequestHolder.get(); } }
調用
無論是哪種方式,都可以直接在Service的方法中執行
HttpServletRequest request = RequestHolder.getHttpServletRequest();
即可直接獲取到線程隔離的HttpServletRequest了。
延伸
類似的功能,在SpringMVC中就有開箱即用的實現。代碼是
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
那么SpringMVC是如何實現的呢?
先看一下RequestContextHolder的源碼(精簡了一下)
public abstract class RequestContextHolder { private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes"); // 重點 private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context"); public static void resetRequestAttributes() { requestAttributesHolder.remove(); // 重點 inheritableRequestAttributesHolder.remove(); } public static void setRequestAttributes(RequestAttributes attributes) { setRequestAttributes(attributes, false); } public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); // 重點 inheritableRequestAttributesHolder.remove(); } } } public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); // 重點 if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } }
主要代碼就是把RequestAttributes對象ThreadLocal化,然后提供了setRequestAttributes()、getRequestAttributes()等靜態方法,來放入或取出ThreadLocal中線程隔離的RequestAttributes。
接下來看一下setRequestAttributes()方法是在什么時候調用的呢?
可以看到setRequestAttributes()被initContextHolders()調用,initContextHolders()又被processRequest()調用,而processRequest()在每次請求時都會被調用,無論是GET、POST、PUT、DELETE還是TRACE、OPTIONS等等。
先來看一下processRequest()方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // 重點1 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 重點2 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); // 重點3 try { doService(request, response); // 執行請求 } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); // 重點4 if (requestAttributes != null) { requestAttributes.requestCompleted(); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { if (asyncManager.isConcurrentHandlingStarted()) { logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } } publishRequestHandledEvent(request, startTime, failureCause); // 發布請求處理完成事件 } }
重點1
在set之前就先get,通常為null。
重點2
直接看buildRequestAttributes()方法的實現
protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) { if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) { return new ServletRequestAttributes(request); // 重點 } else { return null; // preserve the pre-bound RequestAttributes instance } }
ServletRequestAttributes的代碼不再去看了,它就是RequestAttributes接口的實現類,只是對HttpServletRequest對象(還有HttpSession)的一個包裝。
重點3
直接看initContextHolders()方法的實現
private void initContextHolders(HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); // 重點 } if (logger.isTraceEnabled()) { logger.trace("Bound request context to thread: " + request); } }
調用RequestContextHolder.setRequestAttributes()方法,把requestAttributes對象放入。this.threadContextInheritable默認是false。
即把HttpServletRequest的封裝對象ServletRequestAttributes與當前線程綁定。
重點4
private void resetContextHolders(HttpServletRequest request, LocaleContext prevLocaleContext, RequestAttributes previousAttributes) { LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable); RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable); // 重點 if (logger.isTraceEnabled()) { logger.trace("Cleared thread-bound request context: " + request); } }
在請求執行完畢后,再次調用RequestContextHolder.setRequestAttributes(),但由於previousAttributes為null,所以,這里相當於調用RequestContextHolder.setRequestAttributes(null, false)。
再回顧一下setRequestAttributes()方法。
public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } }
參數attributes為null,就會調用resetRequestAttributes(),來清理當前線程引用的RequestAttributes。
至此,SpringMVC是如何實現直接獲取HttpServletRequest對象的源碼,就分析完了。和我們自己實現的思路差不多,只不過多繞了幾個彎而已。
原文鏈接:https://my.oschina.net/ojeta/blog/801640
