文档章节

通过RequestContextHolder直接获取HttpServletRequest对象

胡桃同学
 胡桃同学
发布于 2016/12/07 18:09
字数 1275
阅读 447
收藏 1

问题

朋友遇到一个问题:他想在Service方法中使用HttpServletRequest的API,但是又不想把HttpServletRequest对象当作这个Service方法的参数传过来,原因是这个方法被N多Controller调用,加一个参数就得改一堆代码。一句话:就是他懒。不过,这个问题该这么解决呢?

思考

不把HttpServletRequest当作参数传过来,这意味着要在Service的方法中直接获取到HttpServletRequest对象。
我们知道,一次请求,Web应用服务器就会分配一个线程去处理。也就是说,在Service方法中获取到的HttpServletRequest对象需要满足:线程内共享,线程间隔离
这恰恰是ThreadLocal的应用场景。

思路

那么,就需要在请求执行之前获取到HttpServletRequest,把它set()到某个类的ThreadLocal类型的静态成员中,使用的时候直接通过静态方式访问到这个ThreadLocal对象,调用它的get()方法,即可获取到线程隔离的HttpServletRequest了。最后,在请求结束后,要调用ThreadLocalremove()方法,清理资源引用。

实现

方式一 利用ServletRequestListener实现

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

public class RequestHolder implements ServletRequestListener {

    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();
    
    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
        HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
        httpServletRequestHolder.set(request); // 绑定到当前线程
    }
    
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
        httpServletRequestHolder.remove(); // 清理资源引用
    }
    
    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

方式二 利用Filter实现

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class RequestHolder implements Filter {

    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        httpServletRequestHolder.set((HttpServletRequest) request); // 绑定到当前线程
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
            httpServletRequestHolder.remove(); // 清理资源引用
        }
    }

    @Override
    public void destroy() {
    }

    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

方式三 利用SpringMVC的拦截器实现

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class RequestHolder extends HandlerInterceptorAdapter {

    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
        throws Exception {
        httpServletRequestHolder.set(request); // 绑定到当前线程
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
            Object handler, Exception ex) 
        throws Exception {
        httpServletRequestHolder.remove(); // 清理资源引用
    }
    
    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

调用

无论是哪种方式,都可以直接在Service的方法中执行

HttpServletRequest request = RequestHolder.getHttpServletRequest();

即可直接获取到线程隔离的HttpServletRequest了。

延伸

类似的功能,在SpringMVC中就有开箱即用的实现。代码是

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

那么SpringMVC是如何实现的呢?
先看一下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);
    }

    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;
    }
}

主要代码就是把RequestAttributes对象ThreadLocal化,然后提供了setRequestAttributes()getRequestAttributes()等静态方法,来放入或取出ThreadLocal中线程隔离的RequestAttributes
接下来看一下setRequestAttributes()方法是在什么时候调用的呢?
setRequestAttributes()
可以看到setRequestAttributes()initContextHolders()调用,initContextHolders()又被processRequest()调用,而processRequest()在每次请求时都会被调用,无论是GET、POST、PUT、DELETE还是TRACE、OPTIONS等等。
先来看一下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(); // 重点1
    ServletRequestAttributes requestAttributes = 
            buildRequestAttributes(request, response, previousAttributes); // 重点2

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

    initContextHolders(request, localeContext, requestAttributes); // 重点3

    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 {
        resetContextHolders(request, previousLocaleContext, previousAttributes); // 重点4
        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, startTime, failureCause); // 发布请求处理完成事件
    }
}

重点1

set之前就先get,通常为null

重点2

直接看buildRequestAttributes()方法的实现

protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request, HttpServletResponse response, 
            RequestAttributes previousAttributes) {
    if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
        return new ServletRequestAttributes(request); // 重点
    }
    else {
        return null;  // preserve the pre-bound RequestAttributes instance
    }
}

ServletRequestAttributes的代码不再去看了,它就是RequestAttributes接口的实现类,只是对HttpServletRequest对象(还有HttpSession)的一个包装。

重点3

直接看initContextHolders()方法的实现

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);
    }
}

调用RequestContextHolder.setRequestAttributes()方法,把requestAttributes对象放入。this.threadContextInheritable默认是false
即把HttpServletRequest的封装对象ServletRequestAttributes与当前线程绑定。

重点4

private void resetContextHolders(HttpServletRequest request, LocaleContext prevLocaleContext, 
            RequestAttributes previousAttributes) {
    LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
    RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable); // 重点
    if (logger.isTraceEnabled()) {
        logger.trace("Cleared thread-bound request context: " + request);
    }
}

在请求执行完毕后,再次调用RequestContextHolder.setRequestAttributes(),但由于previousAttributesnull,所以,这里相当于调用RequestContextHolder.setRequestAttributes(null, false)
再回顾一下setRequestAttributes()方法。

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();
        }
    }
}

参数attributesnull,就会调用resetRequestAttributes(),来清理当前线程引用的RequestAttributes

至此,SpringMVC是如何实现直接获取HttpServletRequest对象的源码,就分析完了。和我们自己实现的思路差不多,只不过多绕了几个弯而已。

© 著作权归作者所有

共有 人打赏支持
胡桃同学
粉丝 2
博文 12
码字总数 41989
作品 0
朝阳
技术主管
私信 提问
如何在SpringMVC中获取request对象

如何在SpringMVC中获取request对象 1.注解法 Java代码 收藏代码 @Autowired private HttpServletRequest request; 2. 在web.xml中配置一个监听 Xml代码 收藏代码 <listener> <listener-class......

luck2014
2016/03/16
34
0
Spring MVC 4 获取 Request 和 Reponse 对象方法

这里主要说一下在非Controller层中如何获取Response对象的方法,之前在项目开发过程中,第三方类库需要用到Response对象,于是度娘寻找如何获取该对象,有以下几种方式都不行,没有去查源码,...

华山猛男
2016/12/20
58
0
springmvc 从本地线程获取HttpServletRequest , HttpServletResponse 对象

你是否遇到过service层需要request请求对象呢 ? --当然笔者认为这样的编码习惯是不好的编码习惯。 当然本文的重点不是讨论编码习惯的问题。 不多说了,直接上代码吧 HttpServletRequest req...

随身听1111
2017/11/07
0
0
spring中常用的util

1: spring中获取ApplicationContext public class ApplicationContextUtil implements ApplicationContextAware 即可 2: spring mvc中获取Request <listener> <listener-class>org.springfr......

hebad
2015/10/21
168
0
bboss mvc获取request,session,response,pageContext对象方法

本文介绍基于bboss mvc后台java程序如何获取request,session,response,pageContext对象。 1.组件及方法 组件:org.frameworkset.web.servlet.context.RequestContextHolder 基于bboss mvc的后......

bboss
2013/07/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

EOS官方钱包keosd

EOS官方钱包的名称是keosd,它负责管理你的私钥,并且帮你进行交易的签名。 不过不幸的是,keosd钱包对普通用户并不友好,它是一个命令行程序,目前还没有像以太坊的mist那样的图形化界面,而...

汇智网教程
今天
28
0
ArrayList的实现原理以及实现线程安全

一、ArrayList概述 ArrayList是基于数组实现的,是一个动态的数字,可以自动扩容。 ArrayList不是线程安全的,效率比较高,只能用于单线程的环境中,在多线程环境中可以使用Collections.syn...

一看就喷亏的小猿
今天
36
0
Netty 备录 (一)

入职新公司不久,修修补补1个月的bug,来了点实战性的技术---基于netty即时通信 还好之前对socket有所使用及了解,入手netty应该不是很难吧,好吧,的确有点难,刚看这玩意的时候,可能都不知道哪里...

_大侠__
昨天
42
0
Django简单介绍和用户访问流程

Python下有许多款不同的 Web 框架。Django是重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django。 Django是一个开放源代码的Web应用框架,由Python写成。 Django遵守BSD版权,初...

枫叶云
昨天
54
0
Spring Cloud Stream消费失败后的处理策略(四):重新入队(RabbitMQ)

应用场景 之前我们已经通过《Spring Cloud Stream消费失败后的处理策略(一):自动重试》一文介绍了Spring Cloud Stream默认的消息重试功能。本文将介绍RabbitMQ的binder提供的另外一种重试...

程序猿DD
昨天
25
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部