參考地址 : https://my.oschina.net/ojeta/blog/801640
自身理解 : Web應用流程(以SSM為例) :
請求 ➡ Controller ---Controller中可以獲取到當前的request,response,session,SrevletContext等
⬇
Controller中調用Service層,Service層調用Dao層后進行數據處理
⬇ ➡問題: Controller中可以獲取到request等,但在Service中要使用當前線程中的request等,怎么辦?
請求處理並響應過程中從 M到V到C 過程中都是在一個線程內的
而此時要達到的目的是 : 線程中共享 線程間隔離
⬇
此時就想到了ThreadLocal
⬇
定義一個類實現RequestListener監聽器,類中有ThreadLocal,當一個請求生成時就裝入ThreadLocal,
在此線程內,不論Dao,Service,Controller都可以通過此類而獲取到
實現了線程間隔離,線程內共享
SpringMVC中就實現了此種思想的功能
public static ServletRequestAttributes getServletRequestAttributes() {
return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
}
RequestContextHodler就是上面所說的那個類,而內部有ThreadLocal屬性,可通過getRequestAttributes()方法獲取到此線程的req
工具類
public class WebUtils { public static ServletRequestAttributes getServletRequestAttributes() { return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); } /** * 得到當前線程的請求對象 * * @return */ public static HttpServletRequest getHttpServletRequest() { return getServletRequestAttributes().getRequest(); } /** * 得到訪問的ip地址 * * @return */ public static String getRequestIp() { HttpServletRequest request = getHttpServletRequest(); String ip = null; ip = request.getHeader("x-forwarded-for"); if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) { ip = request.getHeader("Proxy-Client-IP"); } if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) { ip = request.getHeader("WL-Proxy-Client-IP"); } if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) { ip = request.getRemoteAddr(); if (ip.equals("127.0.0.1")) { InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ip = inet.getHostAddress(); } } if ((ip != null) && (ip.length() > 15)) { if (ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } } return ip; } /** * 得到當前線程的響應對象 */ public static HttpServletResponse getHttpServletResponse() { return getServletRequestAttributes().getResponse(); } /** * 得到session對象 */ public static HttpSession getHttpSession() { return getHttpServletRequest().getSession(); } /** * 得到servletContext對象 */ public static ServletContext getServletContext() { return getHttpServletRequest().getServletContext(); } }
問題
朋友遇到一個問題:他想在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
對象的源碼,就分析完了。和我們自己實現的思路差不多,只不過多繞了幾個彎而已。