【小家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
初始化绑定一些通用参数的操作,这样子使用者可以在任意地方,拿到这些公用参数了,可谓特别的方便。
在下面这篇博文讲解
Spring MVC执行流程 源码分析
中,就明确的讲述到了它的初始化过程~
小伙伴可以先自行先思考一个问题: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
继续继承的子类,没什么好说的,唯一就是复写了此方法:
@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");