我是靠谱客的博主 朴素枫叶,这篇文章主要介绍通过RequestContextHolder直接获取HttpServletRequest对象,现在分享给大家,希望可以做个参考。

为什么80%的码农都做不了架构师?>>>   hot3.png

问题

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

思考

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

思路

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

实现

方式一 利用ServletRequestListener实现

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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实现

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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的拦截器实现

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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的方法中执行

复制代码
1
2
HttpServletRequest request = RequestHolder.getHttpServletRequest();

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

延伸

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

复制代码
1
2
3
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

那么SpringMVC是如何实现的呢?
先看一下RequestContextHolder的源码(精简了一下)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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()方法

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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()方法的实现

复制代码
1
2
3
4
5
6
7
8
9
10
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()方法的实现

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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

复制代码
1
2
3
4
5
6
7
8
9
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()方法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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对象的源码,就分析完了。和我们自己实现的思路差不多,只不过多绕了几个弯而已。

转载于:https://my.oschina.net/ojeta/blog/801640

最后

以上就是朴素枫叶最近收集整理的关于通过RequestContextHolder直接获取HttpServletRequest对象的全部内容,更多相关通过RequestContextHolder直接获取HttpServletRequest对象内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部