前言
今天看项目代码时,发现有一组代码获取前端参数时用的不是经典的变量接收的方式,而是使用的RequestContextHolder, 特写了一篇文章记录下学习过程。
前情回顾
都有哪些方式获得前端传来的参数?
获取request
一共有两种获得request的方法,一种是通过方法的参数列表来获取
// 必须在Controller中
@GetMapping("/1")
public R hello(HttpServletRequest request) throws IOException {
// 读取body
// request.getReader().lines().forEach(System.out::println);
String name = request.getParameter("name");
return R.ok(String.format("Hello, %s!", name == null ? "World" : name));
}
另一种是通过Spring的依赖注入来获取
// 必须在bean中
@Resource
private HttpServletRequest request;
@GetMapping("/2")
public R hello2() {
String name = request.getParameter("name");
return R.ok(String.format("Hello, %s!", name == null ? "World" : name));
}
获取到request后,我们就可以通过request的api来获取需要的参数了
参数注入
通过在参数列表直接注入具体参数,最常用,包括注解@RequestBody, @RequestParam, @PathVariable
// 必须在Controller中
@GetMapping("/3")
public R hello3(String name) {
return R.ok(String.format("Hello, %s!", name == null ? "World" : name));
}
RequestContextHolder
RequestContextHolder是SpringMVC的一个抽象类
public abstract class RequestContextHolder, 获取此对象不需要类是Bean,也不需要类是Controller,对其的使用将更加自由; 此类通常用在static工具类中以随时获取变量
简单使用示例
下方是简单使用示例。
主要是从RequestContextHolder->ServletRequestAttributes->HttpServletRequest或者RequestContextHolder->RequestAttributes
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
// RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
//从session里面获取对应的值
String name = (String) attributes.getAttribute("name", RequestAttributes.SCOPE_SESSION);
// 类型转换
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
//获取到Request对象
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取到Response对象
HttpServletResponse response = servletRequestAttributes.getResponse();
//获取到Session对象
HttpSession session = request.getSession();
getRequestAttributes
//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
// ......
/**
* 返回当前绑定到线程的 RequestAttributes。
* 如果没有绑定则返回 null
*/
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
在getRequestAttributes方法中会尝试在在 requestAttributesHolder 中获取,如果不存在,就去inheritableRequestAttributesHolder (可以获得父线程中的变量)中获取
currentRequestAttributes
/**
* 返回当前绑定到线程的 RequestAttributes。
* 如果有的话,返回之前`RequestAttributes` 实例。
* 如果没有,则回退查找当前的 JSF FacesContext(如果存在)。
* 还没有,抛出异常。
*/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: " +
"Are you referring to request attributes outside of an actual web request, " +
"or processing a request outside of the originally receiving thread? " +
"If you are actually operating within a web request and still receive this message, " +
"your code is probably running outside of DispatcherServlet: " +
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
在currentRequestAttributes方法中会尝试在在 requestAttributesHolder 中获取,如果不存在,就去尝试去jsf获取,否则抛出异常
原理
那么,RequestAttributes是如何被放入RequestContextHolder中的呢???
众所周知,在传统的Java Web代码编写中,前端传来的参数会先进入servlet的doGet()或doPost()方法
public class MyServlet extends HttpServlet{} 传统写法。
所以先去DispatcherServlet看看,但DispatcherServlet并没有去处理doGet()或doPost()
那就再去父类FrameworkServlet看看
发现了,顺着方法继续找
我们需要的东西就在这里了
@Nullable
protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable RequestAttributes previousAttributes) {
return previousAttributes != null && !(previousAttributes instanceof ServletRequestAttributes) ? null : new ServletRequestAttributes(request, response);
}
这个方法会使用ServletRequestAttributes把request与response包装起来,然后
然后顺利把我们需要的参数封装进入
RequestContextHolder的两个ThreadLocal
//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
总结
先将
request和response封装到ServletRequestAttributes。再将ServletRequestAttributes绑定到RequestContextHolder类的两个ThreadLocal中,从而通过ThreadLocal的get方法获取ServletRequestAttributes。