聊聊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提供的两个类。

参考:

 

posted @ 2021-12-10 18:13  残城碎梦  阅读(959)  评论(0编辑  收藏  举报