前言
最近做項目,springboot項目,本來我們在controller的requestmapping取參數值或者返回寫時,使用方法參數,但是發現老項目直接注入了成員變量,Spring本身是單例的,如果是成員變量注入,那么也是單例的,怎么實現不同的請求讀取不同的參數呢,如果實現線程安全呢,筆者立馬想到了ThreadLocal,但是如果要說就是這個原理,那么必須源碼證明。
準備demo
簡單寫一個demo
@RestController
public class DemoController {@Autowiredprivate HttpServletRequest request;@GetMapping("/hello")public String demo(String param) {request.getParameterMap().forEach((k,v)-> System.out.println(k + " : " + Arrays.toString(v)));return param + ":hello";}
}
只寫了?HttpServletRequest,實際上HttpServletResponse亦是如此。
分析demo,注入的HttpServletRequest是接口類型,那么在Boot啟動中就會動態代理實現,由于是接口,可以推測是JDK動態代理,debug果然如此。
源碼分析
根據debug,JDK動態代理注入了接口的實現類,關鍵在于InvocationHandler,Spring使用如下:
org.springframework.beans.factory.support.AutowireUtils.ObjectFactoryDelegatingInvocationHandler
里面有關鍵代碼
return method.invoke(this.objectFactory.getObject(), args);
this.objectFactory.getObject()這句決定線程安全
看看Spring Bean下JDK是怎么動態代理注入的
可以看到JDK動態代理在Spring注入的時候,把這個factory注入了InvocationHandler
其中的handler的invoke方法,這里實際上還要其他類的讀取埋點。
這里的invoke僅僅是反射,關鍵還是?HttpServletRequest的對象來源,跟蹤讀取邏輯
org.springframework.web.context.request.RequestContextHolder
到此就明確了,注入的成員變量,動態代理后使用Threadlocal處理,所以線程安全,因為每次請求都是線程請求,那么原始的HttpServletRequest對象怎么塞進去的呢,就要看filter的了
org.springframework.web.filter.RequestContextFilter
在doFilter時,執行
同理在org.springframework.web.servlet.FrameworkServlet也會再次讀取和寫入
這里是為了,如果filter被去除的時候可以有值,再次保底,并且在結束時rest
?只不過這個rest有點奇怪
對象并沒有清除,還在,說明即使?FrameworkServlet后還可以獲取,因為有filter可能會在這個后面執行,如果干掉,很可能就不能讀取了。會在RequestContextFilter后面remove;如果我們去掉這個filter,那么需要自定義一個filter實現remove防止內存泄漏。
清除當前線程及子線程ThreadLocal
public static void resetRequestAttributes() {requestAttributesHolder.remove();inheritableRequestAttributesHolder.remove(); }
至此?HttpServletRequest的成員變量注入邏輯,即ThreadLocal變量,所以請求可以正常訪問
總結
實際上這種用法很多項目都用了,只不過我們寫代碼下意思的通過方法參數來規避線程安全性,這種想法是有益的,可以從源頭規避風險,不過實際上Spring也幫我們考慮了這個問題,相當于使用RequestContextFilter做了保底措施。源碼分析實際上是知所以然,尤其是我們自己寫公共SDK時,可以把這種設計放在代碼中,實現優雅和保底邏輯。