【走近Spring】Spring MVC之RequestContextHolder和LocaleContextHolder的使用詳解
在Java Web的開發中,我們大都執行著三層的開發模式(Controller、Service、Dao)。
在實際開發中:有不少小夥伴想在Service層或者某個工具類層裡獲取HttpServletRequest物件,甚至response的都有。
其中一種方式是,把request當作入參,一層一層的傳遞下去。不過這種有點費勁,且做起來很不優雅。這裡介紹另外一種方案:RequestContextHolder,任意地方使用如下程式碼:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
類似的,LocaleContextHolder是用來處理Local的上下文容器。
RequestContextHolder使用以及原始碼分析
RequestContextHolder顧名思義,持有上下文的Request容器。使用是很簡單的,它所有方法都是static的,該類主要維護了兩個全域性容器(基於ThreadLocal):
// jsf是JSR-127標準的一種使用者介面框架 過時的技術,所以此處不再做討論 private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); //現成和request繫結的容器 private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes"); // 和上面比較,它是被子執行緒繼承的request Inheritable:可繼承的 private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<>("Request context");
現在主要是它的一些get/set方法之類的:
public static void resetRequestAttributes() { requestAttributesHolder.remove(); inheritableRequestAttributesHolder.remove(); } // 把傳入的RequestAttributes和當前執行緒繫結。 注意這裡傳入false:表示不能被繼承 public static void setRequestAttributes(@Nullable RequestAttributes attributes) { setRequestAttributes(attributes, false); } //相容繼承和非繼承 只要得到了就成 public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } //在沒有jsf的時候,效果完全同getRequestAttributes() 因為jsf幾乎廢棄了,所以效果可以說一致 public static RequestAttributes currentRequestAttributes() throws IllegalStateException;
相關使用
DispatcherServlet在處理請求的時候,父類FrameworkServlet#processRequest就有向RequestContextHolder初始化繫結一些通用引數的操作,這樣子使用者可以在任意地方,拿到這些公用引數了,可謂特別的方便。
小夥伴可以先自行先思考一個問題:request和response是怎麼樣設定進去的呢?
另外監聽器org.springframework.web.context.request.RequestContextListener(它是一個ServletRequestListener)裡也有所體現,我們只需要配置上此監聽器即可(因為DispatcherServlet裡有處理,所以此監聽器加不加,無所謂了~)。
使用誤區
場景描述一:在一個商品編輯頁面,提交一個有附件的表單,這個時候通過RequestHolder.getRequest().getParameter()得不到引數值,這是為何?
其實使用過的我們發現,這麼操作大部分情況下都是好使的,但是如果是檔案上傳,在DispatcherServlet裡會把request包裝成MultipartHttpServletRequest,同時content-type為multipart/form-data,因此這個時候getParameter()就失效了~
根本原因:checkMultipart()方法返回的是new出來的一個新的request,所以根本就不再是原來的引用了
場景描述二:在自己新啟的執行緒裡,是不能使用request物件的,當然也就不能使用RequestContextHolder
去獲取到請求域物件了,需要稍加註意
相關類:RequestAttributes
RequestAttributes該介面定義了一些比如get/setAttribute()的便捷方法。它有很多子類,比如我們最常用的ServletRequestAttributes有較大的擴充套件,裡面代理了request和response很多方法:
public final HttpServletRequest getRequest() {
return this.request;
}
@Nullable
public final HttpServletResponse getResponse() {
return this.response;
}
@Override
public Object getAttribute / setAttribute(String name, int scope) { ... }
getAttributeNames;
ServletWebRequest是ServletRequestAttributes的子類,還實現了介面NativeWebRequest(提供一些獲取Native Request的方法,其實沒太大作用):它代理得就更加的全一些,比如:
@Nullable
public HttpMethod getHttpMethod() {
return HttpMethod.resolve(getRequest().getMethod());
}
@Override
@Nullable
public String getHeader(String headerName) {
return getRequest().getHeader(headerName);
}
getHeaderValues/getParameter/... 等等一些列更多的代理方法
DispatcherServletWebRequest是ServletWebRequest的子類,唯一就是複寫了此方法:
@Override
public Locale getLocale() {
return RequestContextUtils.getLocale(getRequest());
}
StandardServletAsyncWebRequest這個和非同步攔截器相關,屬於非同步上下文範疇。
LocaleContextHolder使用以及原始碼分析