@Autowired HttpSession & HttpServletRequest原理


问题

在开发Controller的时候,经常会需要使用到HttpSessionHttpServletRequest这两个接口的实例对象。在工作室中的项目,无论是师兄留下的代码还是自己编写,基本都是使用比较简单的,就是直接从方法参数中获取,就像下面这样:

这不失为一个好办法,但也有缺点:一是如果多个方法需要会话操作(如权限接口)的话会显得冗余,而是加长了方法参数列表降低了代码可读性。毕竟Controller中的方法一般都会代表前台某个参数。所以我想寻求一种更好的办法。Spring的依赖注入很方便,那么,Session和Request对象能不能依赖注入呢?

只有一年多开发经验的我,一开始当然以为是不行的。

因为Session和Request的生命周期是会话级别和请求级别,而对Session、Request进行依赖注入的对象是Controller,是单例对象。我们都知道,某个对象依赖注入一个生命周期不如自己长的对象,最后的结果是被注入的对象生命周期变得和注入的对象生命周期一样长。假如有两个不同类的实例A和B,A这种持有B的引用,B注入进A的引用里,那么在A生命周期结束前Spring不会再去新注入B对象,如果A是单例对象,B定义为prototype,那么B也会变成单例,这显然违背我们把B定义为非单例的初衷。另外,在Webx3 开发文档 4.4.2 中也说明原生Spring注入无法满足这种情况,Webx通过<request-contexts>进行Turbine增强才可以注入。也让我一开始对“Spring无法注入Session”深信不疑。

直到我网上搜到一些所谓获取session方法大全博客,里面赫然有着autowired注入这种方法。

这 ..

说好的不可以呢?

于是默默的写了一大堆测试代码,找了很多文档资料,最终搞明白了其中的道理,分享如下。先把结果写出来,如果有兴趣可以再往下看测试代码。

Scoped Proxy

简单介绍一个Spring对单例对象注入非单例实例的一个解决方案:Scoped Proxy.

Scoped proxy使用代理模式,不去直接注入这个类的实例,而是每次使用的时候都去创建一个代理对象,当调用注入方法时,会通过其他途径去调用原生对象的方法。所以我们autowired的时候不是真的注入自己的对象,只是一个壳而已。

自己使用bean scoped的例子可以查看开发文档:Spring4 开发文档: bean scope
简洁的解释可以看: spring scoped proxy bean

结论

知道了Scoped Proxy,想必对这个的实现也了解的差不多了。Spring巧妙地注入了一个装饰器代理了真实对象的操作,只在需要的时候获取真实的Request和Session对象,获取方法Servlet自身实现好了,和Servlet类获取Request和Session应该大同小异。所以获取Session和Request的几种方法中:

- @Autowired获取到的是装饰器对象
- 方法参数获取到的是真实对象
- 无论是@Autowired还是方法参数的request,request.getSession()获取到的是真实Session对象

shiro session

实际测试中,发现Spring3和Spring4还是有差异的。

虽然都是注入了一个装饰器对象,但是Spring3中,无论是装饰器调用的,还是方法参数获取到的,或者是request.getSession()获取到的HttpSession都是真实的HttpSession对象,hashCode都是不变的,非分布式的单服务器实例下,一个会话只有一个HttpSession

整合Shiro以后,无论是装饰器调用的,还是方法参数获取到的,或者是request.getSession()获取到的HttpSession都是**代理对象**,每次获取到的session,只是"看上去"和真实Session一样,实际上每次执行controller获取到的session都是不同的实例,hashcode是不一样的。

这是因为整合Shiro以后,StoppingAwareProxiedSession继承了原来的HttpSession实现类(Tomcat中是StandardSession),属于shiro自定义的session的子类。通过这个代理对象的源码,所有与session相关的方法都是通过它内部委托类delegate进行的,而到delegate的类型其实也是 org.apache.catalina.session.StandardSessionFacade ,也就是说,两者在操作session时,都是用同一个类型的session。

Shiro用了一层代理来包装HttpSessionHttpServletRequest这两个接口实例,如果用@Autowired注入,算上@Autowired的装饰器,是两层代理。

Shiro包装HttpSession

测试

测试demo和测试数据见:我的github

 

声明:夜语|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - @Autowired HttpSession & HttpServletRequest原理


我不和你谈事,只想和你谈心。🍂