聊聊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;

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提供的兩個類。

參考:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM