1. 程式人生 > 其它 >【走近Spring】Spring MVC之RequestContextHolder和LocaleContextHolder的使用詳解

【走近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使用以及原始碼分析