作者:编程迷思
www.cnblogs.com/kismetv/p/8757260.html
本文将介绍在Spring MVC开发的Web零碎中,获取request对象的几种办法,并探讨其线程安全性。
一、概述
在应用Spring MVC开发Web零碎时,常常须要在解决申请时应用request对象,比方获取客户端IP地址、申请的URL、header中的属性(如cookie、受权信息)、body中的数据等。因为在Spring MVC中,解决申请的Controller、Service等对象都是单例的,因而获取request对象时最须要留神的问题,便是request对象是否是线程平安的:当有大量并发申请时,是否保障不同申请/线程中应用不同的request对象。
这里还有一个问题须要留神:后面所说的“在解决申请时”应用request对象,到底是在哪里应用呢?思考到获取request对象的办法有渺小的不同,大体能够分为两类:
1、在Spring的Bean中应用request对象:既包含Controller、Service、Repository等MVC的Bean,也包含了Component等一般的Spring Bean。为了不便阐明,后文中Spring中的Bean一律简称为Bean。
2、在非Bean中应用request对象:如一般的Java对象的办法中应用,或在类的静态方法中应用。
此外,本文探讨是围绕代表申请的request对象开展的,但所用办法同样实用于response对象、InputStream/Reader、OutputStream/ Writer等;其中InputStream/Reader能够读取申请中的数据,OutputStream/Writer能够向响应写入数据。
最初,获取request对象的办法与Spring及MVC的版本也有关系;本文基于Spring4进行探讨,且所做的试验都是应用4.1.1版本。
二、如何测试线程安全性
既然request对象的线程平安问题须要特地关注,为了便于前面的探讨,上面先阐明如何测试request对象是否是线程平安的。
测试的基本思路,是模仿客户端大量并发申请,而后在服务器判断这些申请是否应用了雷同的request对象。
判断request对象是否雷同,最直观的形式是打印出request对象的地址,如果雷同则阐明应用了雷同的对象。然而,在简直所有web服务器的实现中,都应用了线程池,这样就导致先后达到的两个申请,可能由同一个线程解决:在前一个申请解决实现后,线程池发出该线程,并将该线程重新分配给了前面的申请。而在同一线程中,应用的request对象很可能是同一个(地址雷同,属性不同)。因而即使是对于线程平安的办法,不同的申请应用的request对象地址也可能雷同。
为了防止这个问题,一种办法是在申请处理过程中使线程休眠几秒,这样能够让每个线程工作的工夫足够长,从而防止同一个线程调配给不同的申请;另一种办法,是应用request的其余属性(如参数、header、body等)作为request是否线程平安的根据,因为即使不同的申请先后应用了同一个线程(request对象地址也雷同),只有应用不同的属性别离结构了两次request对象,那么request对象的应用就是线程平安的。本文应用第二种办法进行测试。
客户端测试代码如下(创立1000个线程别离发送申请):
服务器中Controller代码如下(临时省略了获取Request对象的代码):
如果request对象线程平安,服务器中打印后果如下所示:
如果存在线程平安问题,服务器中打印后果可能如下所示:
如无非凡阐明,本文前面的代码中将省略掉测试代码。
三、办法1:Controller中加参数
1、代码示例
这种办法实现最简略,间接上Controller代码:
该办法实现的原理是,在Controller办法开始解决申请时,Spring会将request对象赋值到办法参数中。除了request对象,能够通过这种办法获取的参数还有很多,具体能够参见:https://docs.spring.io/spring...
Controller中获取request对象后,如果要在其余办法中(如service办法、工具类办法等)应用request对象,须要在调用这些办法时将request对象作为参数传入。
2、线程安全性
测试后果:线程平安
剖析:此时request对象是办法参数,相当于局部变量,毫无疑问是线程平安的。线程平安的 Map 能够点此查看这篇文章。
3、优缺点
这种办法的次要毛病是request对象写起来冗余太多,次要体现在两点:
(1)如果多个controller办法中都须要request对象,那么在每个办法中都须要增加一遍request参数
(2) request对象的获取只能从controller开始,如果应用request对象的中央在函数调用层级比拟深的中央,那么整个调用链上的所有办法都须要增加request参数
实际上,在整个申请解决的过程中,request对象是贯通始终的;也就是说,除了定时器等非凡状况,request对象相当于线程外部的一个全局变量。而该办法,相当于将这个全局变量,传来传去。点击此处查看公众号全套Spring系列收费技术教程。
四、办法2:主动注入
1、代码示例
先上代码:
2、线程安全性
测试后果:线程平安
剖析:在Spring中,Controller的scope是singleton(单例),也就是说在整个web零碎中,只有一个TestController;然而其中注入的request却是线程平安的,起因在于:
应用这种形式,当Bean(本例的TestController)初始化时,Spring并没有注入一个request对象,而是注入了一个代理(proxy);当Bean中须要应用request对象时,通过该代理获取request对象。
上面通过具体的代码对这一实现进行阐明。
在上述代码中退出断点,查看request对象的属性,如下图所示:
在图中能够看出,request实际上是一个代理:代理的实现参见AutowireUtils的外部类ObjectFactoryDelegatingInvocationHandler:
也就是说,当咱们调用request的办法method时,实际上是调用了由objectFactory.getObject()生成的对象的method办法;objectFactory.getObject()生成的对象才是真正的request对象。
持续察看上图,发现objectFactory的类型为WebApplicationContextUtils的外部类RequestObjectFactory;而RequestObjectFactory代码如下:
其中,要取得request对象须要先调用currentRequestAttributes()办法取得RequestAttributes对象,该办法的实现如下:
生成RequestAttributes对象的外围代码在类RequestContextHolder中,其中相干代码如下(省略了该类中的无关代码):
通过这段代码能够看出,生成的RequestAttributes对象是线程局部变量(ThreadLocal),因而request对象也是线程局部变量;这就保障了request对象的线程安全性。点击此处查看公众号全套Spring系列收费技术教程。
3、优缺点
该办法的次要长处:
(1)注入不局限于Controller中:在办法1中,只能在Controller中退出request参数。而对于办法2,不仅能够在Controller中注入,还能够在任何Bean中注入,包含Service、Repository及一般的Bean。
(2)注入的对象不限于request:除了注入request对象,该办法还能够注入其余scope为request或session的对象,如response对象、session对象等;并保障线程平安。
(3)缩小代码冗余:只须要在须要request对象的Bean中注入request对象,便能够在该Bean的各个办法中应用,与办法1相比大大减少了代码冗余。
然而,该办法也会存在代码冗余。思考这样的场景:web零碎中有很多controller,每个controller中都会应用request对象(这种场景实际上十分频繁),这时就须要写很屡次注入request的代码;如果还须要注入response,代码就更繁琐了。上面阐明主动注入办法的改良办法,并剖析其线程安全性及优缺点。
五、办法3:基类中主动注入
1、代码示例
与办法2相比,将注入局部代码放入到了基类中。
基类代码:
Controller代码如下;这里列举了BaseController的两个派生类,因为此时测试代码会有所不同,因而服务端测试代码没有省略;客户端也须要进行相应的批改(同时向2个url发送大量并发申请)。
2、线程安全性
测试后果:线程平安
剖析:在了解了办法2的线程安全性的根底上,很容易了解办法3是线程平安的:当创立不同的派生类对象时,基类中的域(这里是注入的request)在不同的派生类对象中会占据不同的内存空间,也就是说将注入request的代码放在基类中对线程安全性没有任何影响;测试后果也证实了这一点。线程平安的 Map 能够点此查看这篇文章。
3、优缺点
与办法2相比,防止了在不同的Controller中反复注入request;然而思考到java只容许继承一个基类,所以如果Controller须要继承其余类时,该办法便不再好用。
无论是办法2和办法3,都只能在Bean中注入request;如果其余办法(如工具类中static办法)须要应用request对象,则须要在调用这些办法时将request参数传递进去。上面介绍的办法4,则能够间接在诸如工具类中的static办法中应用request对象(当然在各种Bean中也能够应用)。点击此处查看公众号全套Spring系列收费技术教程。
六、办法4:手动调用
1、代码示例
2、线程安全性
测试后果:线程平安
剖析:该办法与办法2(主动注入)相似,只不过办法2中通过主动注入实现,本办法通过手动办法调用实现。因而本办法也是线程平安的。
3、优缺点
长处:能够在非Bean中间接获取。毛病:如果应用的中央较多,代码十分繁琐;因而能够与其余办法配合应用。
七、办法5:@ModelAttribute办法
1、代码示例
上面这种办法及其变种(变种:将request和bindRequest放在子类中)在网上常常见到:
2、线程安全性
测试后果:线程不平安
剖析:@ModelAttribute注解用在Controller中润饰办法时,其作用是Controller中的每个@RequestMapping办法执行前,该办法都会执行。因而在本例中,bindRequest()的作用是在test()执行前为request对象赋值。尽管bindRequest()中的参数request自身是线程平安的,但因为TestController是单例的,request作为TestController的一个域,无奈保障线程平安。
八、总结
综上所述,Controller中加参数(办法1)、主动注入(办法2和办法3)、手动调用(办法4)都是线程平安的,都能够用来获取request对象。如果零碎中request对象应用较少,则应用哪种形式均可;如果应用较多,倡议应用主动注入(办法2 和办法3)来缩小代码冗余。如果须要在非Bean中应用request对象,既能够在下层调用时通过参数传入,也能够间接在办法中通过手动调用(办法4)取得。
感觉有帮忙就转发分享一下吧!
近期热文举荐:
1.600+ 道 Java面试题及答案整顿(2021最新版)
2.终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!