共计 4977 个字符,预计需要花费 13 分钟才能阅读完成。
作者:编程迷思
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 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!