先放出这个问题的来源,是我在对Controller进行AOP编程的时候遇到的,大致需求是在切面前获取HttpSession中的对象,但是AOP的切面方法又与servlet无关,怎么获得呢?。又可以延伸出,SpringMVC是怎么获得HttpSession的?虽然平常我们在编写Controller的时候直接写在方法参数就行了,但是底层大致是怎么实现的呢?今天就来探究一下。
先直接抛出答案,怎么在AOP获得HttpSession?
ServletRequestAttributes attr = >(ServletRequestAttributes)RequestContextHolder.currentRequestAttributes(); HttpSession session=attr.getRequest().getSession(true);
看到这个代码,业务方面是解决了,又得到新的问题
HttpSession是怎么保证这个就是本次请求的HttpSession?
RequestContextHolder是什么东西?
1.png首先分析RequestContextHolder这个类,
里面有两个ThreadLocal保存当前线程下的request(这两个request又是怎么放进ThreadLocal的下面再讲)
T@HGC2X97}4ANQ$J%TUB7HF.png有一个getRequestAttributes()来获取ThreadLocal中的对象,保证了这个线程获得的就是当前线程的Request,而HttpSession存在Request中。
3.png在我们最上面解决业务的代码中用到了currentRequestAttributes(),调用了上面的getRequestAttributes()来获得Request。
至此稍微理了一下RequestContextHolder的调用,那么SpringMVC是怎么把request放到ThreadLocal里来的呢?
先回忆下SpringMVC的工作流程
1.http请求过来,请求到DispatcherServlet
2.DispatcherServlet调用HandlerMapping,解析请求对应的Handler
3.解析到对应的Handler之后由HandlerAdapter(也就是我们的平常写的Controller)根据这个Handler处理请求和业务
4.处理完之后返回一个ModelAndView给DispatcherServlet,拿到这个参数后由DispatcherServlet调用ViewResolver
5.ViewResolver解析后返回给DispatcherServlet一个view
6.DispatcherServlet通过这个view渲染视图,再返回给用户
我们经常在Controller里直接写(HttppSession session)来拿到这个HttpSession,那么很明显了,这个应该是DispatcherServlet的功劳_。
33.pngCtrl+Shift+T找到DispatcherServlet,按下F4查看继承关系,可以看到明显父类有HttpServlet,在FrameworkServlet按下Ctrl+O,找到了里面有重写一堆service(),doGet()之类的方法,里面调用了processRequest(request,response)。这就找到线索了嘛,跟进去看看
方法写的有点多,这里我们只关心request这一参数的传递,这里列出每个方法大概的意思,里面有个方法名叫initContextHolders,意思是初始化的方法,还记得一开始讲的那个类叫啥不,RequestContextHolder,我觉得差不多就要发现真相了。注意两个红框框起来的方法哦。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取上一个请求保存的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//建立新的LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取上一个请求保存的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//建立新的RequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//具体设置的方法
initContextHolders(request, localeContext, requestAttributes);
87MNVQH`8_KOLGNIZUN9RSM.png
L{U3~FFS8448DMN~3V%@IR5.png先来看看initContextHolders(),果然嘛,跟之前的肯定有联系,不然怎么通过RequestContextHolder得到的Request中的HttpSession呢,这个时候有眼尖的小伙伴可能会说了,你这里是requestAttributes啊,不是request,我们倒回去看上面的方法,这个requestAttributes到底是个啥?_
A4R~Y}1}Y920H$VW4P0TZSH.png那我们看看这是个啥?哦豁,返回了一个ServletRequestAttributes,大家都长差不多,你有啥不一样??
T`}GM5}B4WYA3835I3YQPCS.png妥了,这个看起来就很心旷神怡,就是初始化了这个类中的request和response嘛,那我们上面这个问题也解决了,ServletRequestAttributes封装了request和response。
还记得我们为什么要找这个ServletRequestAttributes吗,在前面的方法里没有把request放进ThreadLocal里,而是放了这个对象,所以我们能拿到request,甚至能拿到response,还顺便把前面为什么能强转成ServletRequestAttributes都解释了
—————————————————————————————————————————
基本就讲完啦,觉得晕吗?再来梳理一遍
ServletRequestAttributes attr = >(ServletRequestAttributes)RequestContextHolder.currentRequestAttributes(); HttpSession session=attr.getRequest().getSession(true);
1.RequestContextHolder中有ThreadLocal,用来存储本线程的ServletRequestAttributes(这个是reque和response的封装类,里面有request哦,request里面有HttpSession哦)
2.FrameworkServlet继承了HttpServlet,重写了一堆doGet(),doPost()方法,这些重写的方法调用了processRequest(),别管上层是怎么封装的,你在发Http请求的时候肯定会用到这些方法,就调用了嘛,然后就触发了这个方法了。
3.这个方法会先把request和response封装成ServletRequestAttributes类,然后放到RequestContextHolder的ThreadLocal的里面。
4.至此,我们就可以通过调用RequestContextHolder来获得本线程的ServletRequestAttributes啦(再重申一遍,这里面有request和response,request里有HttpSession)
网友评论