在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;
Spring內部使用
DispatcherServlet在處理請求的時候,父類FrameworkServlet#processRequest就有向RequestContextHolder初始化綁定一些通用參數的操作,這樣使用者可以在任意地方,拿到這些公用參數了,可謂特別的方便。
另外監聽器org.springframework.web.context.request.RequestContextListener(它是一個ServletRequestListener)里也有所體現,我們只需要配置上此監聽器即可(因為DispatcherServlet里有處理,所以此監聽器加不加,無所謂了~)。
RequestContextHolder使用誤區
場景描述一:在一個商品編輯頁面,提交一個有附件的表單,這個時候通過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) {...}
@Override
public String[] getAttributeNames(int scope) {...}
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使用以及源碼分析
這個比上面就更簡單些,是來做本地化、國際化的上下文容器。
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<>("LocaleContext");
private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<>("LocaleContext");
//沒有手動調用setDefaultLocale,取值為 Locale#getDefault()
private static Locale defaultLocale;
//同上 默認取值為TimeZone.getDefault()
private static TimeZone defaultTimeZone;
幾乎源碼過程同RequestContextHolder,只需要注意一個方法:
// 我們可以直接從請求域拿到Local上下文,但是也是可以自己傳進來的。。。
public static Locale getLocale(@Nullable LocaleContext localeContext) { ... }
其實這兩個類也可以作為我們的工具來使用,我們集成的時候也可以使用Spring提供的兩個類。
| 參考: |
