通过RequestContextHolder直接获取HttpServletRequest对象
问题
朋友遇到一个问题:他想在Service方法中使用HttpServletRequest的API,但是又不想把HttpServletRequest对象当作这个Service方法的参数传过来,原因是这个方法被N多Controller调用,加一个参数就得改一堆代码。一句话:就是他懒。不过,这个问题该这么解决呢?
思考
不把HttpServletRequest当作参数传过来,这意味着要在Service的方法中直接获取到HttpServletRequest对象。
我们知道,一次请求,Web应用服务器就会分配一个线程去处理。也就是说,在Service方法中获取到的HttpServletRequest对象需要满足:线程内共享,线程间隔离。
这恰恰是ThreadLocal的应用场景。
思路
那么,就需要在请求执行之前获取到HttpServletRequest,把它set()到某个类的ThreadLocal类型的静态成员中,使用的时候直接通过静态方式访问到这个ThreadLocal对象,调用它的get()方法,即可获取到线程隔离的HttpServletRequest了。最后,在请求结束后,要调用ThreadLocal的remove()方法,清理资源引用。
实现
方式一 利用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()被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(),但由于previousAttributes为null,所以,这里相当于调用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();
        }
    }
}
参数attributes为null,就会调用resetRequestAttributes(),来清理当前线程引用的RequestAttributes。
至此,SpringMVC是如何实现直接获取HttpServletRequest对象的源码,就分析完了。和我们自己实现的思路差不多,只不过多绕了几个弯而已。
原文链接:https://my.oschina.net/ojeta/blog/801640
通过RequestContextHolder直接获取HttpServletRequest对象的更多相关文章
- spring几种获取 HttpServletRequest 对象的方式
		以下的 request 实例都编号了,一共 4 种 方式 1.@Autowired 方式2.public void Test(HttpServletRequest request1, HttpServ ... 
- Spring获取HttpServletRequest对象
		<!-- WEB.XML中配置相关的监听机制 --> <listener> <listener-class> org.springframework.web ... 
- spring mvc在普通类中获取HttpServletRequest对象
		如题,需要在web.xml中配置request监听,如下 <listener> <description>spring request监听器</description&g ... 
- Http协议入门、响应与请求行、HttpServletRequest对象的使用、请求参数获取和编码问题
		1 课程回顾 web入门 1)web服务软件作用: 把本地资源共享给外部访问 2)tomcat服务器基本操作 : 启动: %tomcat%/bin/startup.bat 关闭: %tomcat%/ ... 
- 在SpringMVC中获取request对象
		1.注解法 @Autowired private HttpServletRequest request; 2. 在web.xml中配置一个监听 <listener> <listen ... 
- 在SpringMVC中获取request对象的几种方式
		1.最简单的方式(注解法) @Autowired private HttpServletRequest request; 2.最麻烦的方法 a. 在web.xml中配置一个监听 <listene ... 
- 如何在spring中获取request对象
		1.通过注解获取(很简单,推荐): public class Hello {@Autowired HttpServletRequest request; //这里可以获取到request} 2.在w ... 
- 如何在SpringMVC中获取request对象
		1.注解法 @Autowired private HttpServletRequest request; <listener> <listener-class> org.spr ... 
- spring mvc中几种获取request对象的方式
		在使用spring进行web开发的时候,优势会用到request对象,用来获取访问ip.请求头信息等 这里收集几种获取request对象的方式 方法一:在controller里面的加参数 public ... 
随机推荐
- jsignature 中文开发手册
			2017年5月9日21:23:17,最近比较忙,没时间写博客,真的是越来越懒来了 github:https://github.com/brinley/jSignature http://www.unb ... 
- 浅谈Java对象的equals方法
			相等与同一: 如果两个对象具有相同的类型以及相同的属性值,则称这两个对象相等. 如果两个引用对象指的是同一个对象,则称这两个变量同一. ==是一个比较运算符,基本数据类型比较的是值,引用数据类型比较的 ... 
- debian使用nginx创建静态文件存储
			vim /etc/nginx/sites-available/default 在server下添加 location ~ .*\.(gif|jpg|jpeg|png)$ { expires 24h; ... 
- 检查文件是否被修改或者被破坏工具 md5
			检查文件和对应的md5值是否一致. 
- 前端学习历程--css①
			---恢复内容开始--- 本文用自己的理解,总结网上或者自身经历的问题,加以汇总,方便查找: 一.浏览器默认样式 1.浏览器处理css&html a.css作用范围:盒子模式.浮动.定位.背景 ... 
- CentOS 7 之 Systemd 入门教程:实战篇
			开机启动对于那些支持 Systemd 的软件,安装的时候,会自动在/usr/lib/systemd/system目录添加一个配置文件. 如果你想让该软件开机启动,就执行下面的命令(以httpd.ser ... 
- UI框架搭建DAY1
			分析:UI框架主要是为了用户(使用框架的程序猿)更快捷.方便地开发UI,UI框架的好处还在于解耦,使得程序更具有灵活性. UI框架的核心是窗口的管理,窗口管理的主要任务就是显示窗口和关闭窗口. 因为窗 ... 
- 【转】jira插件Zephyr的具体使用
			在工作中,我们通常是在excel表格中编写测试用例,增删改查功能都不错,但保存.管理.共享都不完美,为了让公司领导或其他同事方便查看测试执行情况和测试进度,我们引入了TestLink工具来编写测试用例 ... 
- 给zabbix添加percona监控模板
			简单说明一下给zabbix添加的percona的监控模板. 在percona官方网站有说明怎么安装,这里记录下步骤.首先搭建好的zabbix环境. 监控插件连接 : 链接:https://pan.ba ... 
- 2019 GOALS
			ANNUAL GOAL 生活 1. 养成早睡早起的习惯 2. 体重:43kg 总体来讲希望自己有一个健康的生活方式,良好的饮食.运动习惯. 2019-04-17 18:47:14 UPDATE 3. ... 
