RequestContextHolder获取request

本文深入探讨了Spring MVC框架中RequestContextHolder的工作原理,详细解释了如何通过它在Service层或工具类中获取HttpServletRequest对象,避免了参数的繁琐传递。同时,文章分析了RequestContextHolder与ThreadLocal的关系,以及HttpServletRequest在RequestContextHolder中的设置时机。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在Web开发中,service层或者某个工具类中需要获取到HttpServletRequest对象还是比较常见的。一种方式是将HttpServletRequest作为方法的参数从controller层一直放下传递,不过这种有点费劲,且做起来不是优雅;还有另一种则是RequestContextHolder,直接在需要用的地方使用如下方式取HttpServletRequest即可,使用代码如下:

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
        .getRequestAttributes()).getRequest();

要理解上面的为何可以这么使用,需要理解两个问题:

RequestContextHolder为什么能获取到当前的HttpServletRequest
HttpServletRequest是在什么时候设置到RequestContextHolder

对于第1个问题,熟悉ThreadLocal的人应该很容易看出来这个是ThreadLocal的应用,这个类的原理在上一篇博文(ThreadLocal原理)有讲到,其实很类似上篇博文文末提到的UserContextHolder。
第2个问题应该属于spring-mvc的问题,这个是在spring-mvc执行时设置进去的

源码分析
首先我们先来看下RequestContextHolder的源码,源码如下:

public abstract class RequestContextHolder  {

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");


    public static void resetRequestAttributes() {
        requestAttributesHolder.remove();
        inheritableRequestAttributesHolder.remove();
    }


    public static void setRequestAttributes(RequestAttributes attributes) {
        setRequestAttributes(attributes, false);
    }

    //将RequestAttributes对象放入到ThreadLocal中,而HttpServletRequest和HttpServletResponse等则封装在RequestAttributes对象中,在此处就不对RequestAttributes这个类展开。反正我们需要知道的就是要获取RequestAttributes对象,然后再从RequestAttributes对象中获取到我们所需要的HttpServletRequest即可
    public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        }
        else {
            if (inheritable) {
                inheritableRequestAttributesHolder.set(attributes);
                requestAttributesHolder.remove();
            }
            else {
                requestAttributesHolder.set(attributes);
                inheritableRequestAttributesHolder.remove();
            }
        }
    }

    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }

}

那么在spring-mvc中是怎么实现的呢,我们来简单分析的,想了解具体机制的可以去看看spring-mvc的源码。
我们看下FrameworkServlet这个类,也就是DispatcherServlet的父类,里面有个processRequest方法,根据方法名称我们也可以大概了解到这个是方法用于处理请求的。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        //将RequestAttributes设置到RequestContextHolder
        initContextHolders(request, localeContext, requestAttributes);

        try {
            //具体的业务逻辑
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            //重置RequestContextHolder之前设置RequestAttributes
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }

            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
    
    
    private void initContextHolders(
            HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {

        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
    }


简单看下源码,我们可以知道HttpServletRequest是在执行doService方法之前,也就是具体的业务逻辑前进行设置的,然后在执行完业务逻辑或者抛出异常时重置RequestContextHolder移除当前的HttpServletRequest。

【多线程】ThreadLocal原理
使用
在每个线程的内部有个数据结构为Map的threadLocals变量,以<ThreadLocal,Value>的形式保存着线程变量和其对应的值。
当使用set()方法时:

1.获取到当前线程的threadLocals,类型为Map
2.将这值放到这个Map结构的变量中,key为ThreadLocal对象,value为所有存放的值

当使用get()方法时:

1.获取到当前线程的threadLocals,类型为Map。
2.以ThreadLocal对象为Map的key获取到它的value值。

因为ThreadLocal对象作为Map的key,所以一个ThreadLocal对象只能存放一个值,当存放多个时,会将新值覆盖旧值。

数据结构:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//当前线程为入参,获取当前线程的threadLocals变量
        if (map != null)
         //入参为this,也就是说key为ThreadLocal对象
            map.set(this, value);
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//当前线程为入参,获取当前线程的threadLocals
        if (map != null) {
            //入参为this,也就是说key为ThreadLocal
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;//threadLocals为线程的变量
    }
    
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);//避免内存泄漏,下文有提。
    }

https://www.jianshu.com/p/6bf1adb775e0

在Spring Boot中,如果你想要在处理HTTP请求前或后对`RequestBody`进行自定义操作,你可以使用`@ModelAttribute注解`结合`RequestBodyAdvice`。`RequestBodyAdvice`是一个接口,允许你在请求体被处理之前或之后添加、修改或删除数据。 首先,你需要创建一个实现了`RequestBodyAdvice接口`的类: ```java import org.springframework.http.HttpRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @RestControllerAdvice public class CustomRequestBodyAdvice implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { // 检查是否支持处理RequestBody类型的参数 return parameter.getParameterType().equals(RequestBody.class); } @Override public Object resolveArgument(HttpRequest request, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 这里可以获取原始的RequestBody内容 Object requestBody = request.getBody(); // 对请求体做你想要的操作,比如日志记录、验证等 // 然后返回处理后的请求体 // 返回处理后的请求体给框架继续处理 return requestBody; } } ``` 在这个例子中,`resolveArgument`方法会在每个控制器方法处理`RequestBody`参数之前被调用。你可以在这里获取原始请求体的内容,并对其进行相应的修改。注意,`RequestContextHolder`通常用于获取当前HTTP请求的相关信息,但在`RequestBodyAdvice`上下文中,你直接操作`HttpRequest`即可。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值