SpringMVC:RequestContextHolder

357 阅读3分钟

前言

今天看项目代码时,发现有一组代码获取前端参数时用的不是经典的变量接收的方式,而是使用的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()

image.png

那就再去父类FrameworkServlet看看

image.png 发现了,顺着方法继续找

image.png 我们需要的东西就在这里了

@Nullable
protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable RequestAttributes previousAttributes) {
    return previousAttributes != null && !(previousAttributes instanceof ServletRequestAttributes) ? null : new ServletRequestAttributes(request, response);
}

这个方法会使用ServletRequestAttributesrequestresponse包装起来,然后

image.png 然后顺利把我们需要的参数封装进入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");

总结

先将requestresponse封装到ServletRequestAttributes。再将ServletRequestAttributes绑定到RequestContextHolder类的两个ThreadLocal中,从而通过ThreadLocalget方法获取ServletRequestAttributes

引用

多线程下的 RequestContextHolder

Api文档

RequestContextHolder详解(获取request对象的四种方法)

SpringMVC之RequestContextHolder分析