SpringMVC--HttpServletRequest 「爱情、让人受尽委屈。」 2022-12-03 09:22 62阅读 0赞 原文网址:[SpringMVC--HttpServletRequest\_IT利刃出鞘的博客-CSDN博客][SpringMVC--HttpServletRequest_IT_-CSDN] # 其他网址 # > [servlet入门笔记][servlet] # 获取HttpServletRequest # **其他网址** > [SpringMVC在Controller层中注入request的坑 - sluggarddd的个人页面 - OSCHINA - 中文开源技术交流社区][SpringMVC_Controller_request_ - sluggarddd_ - OSCHINA -] > > [Spring中获取request的几种方法,及其线程安全性分析 - 编程迷思 - 博客园][Spring_request_ - _ -] > [SpringBoot三种获取Request和Response的方法\_jiulanhao的博客-CSDN博客\_springboot获取request对象][SpringBoot_Request_Response_jiulanhao_-CSDN_springboot_request] ## 方案1:统一获取 ## ### **法1:@ControllerAdvice+@ModelAttribute** ### > **其他网址** > > [SpringMVC进阶--注解\_feiying0canglang的博客-CSDN博客][SpringMVC_--_feiying0canglang_-CSDN] ### **法2:拦截器/过滤器** ### > **其他网址** > > [SpringMVC系列--过滤器/拦截器\_feiying0canglang的博客-CSDN博客][SpringMVC_--_feiying0canglang_-CSDN 1] ## 方案2:Controller中获取 ## ### **法1:Controller中加参数** ### **简介** > 该方法实现的原理是,在Controller方法开始处理请求时,Spring会将request对象赋值到方法参数中。除了request对象,可以通过这种方法获取的参数还有很多。 > > Controller中获取request对象后,如果要在其他方法中(如service方法、工具类方法等)使用request对象,需要在调用这些方法时将request对象作为参数传入。 **代码示例** > 这种方法实现最简单,直接上Controller代码: > > package com.example.controller; > > import org.springframework.web.bind.annotation.GetMapping; > import org.springframework.web.bind.annotation.RequestMapping; > import org.springframework.web.bind.annotation.RestController; > > import javax.servlet.http.HttpServletRequest; > > @RestController > @RequestMapping > public class HelloController { > > @GetMapping("/test1") > public String test1(HttpServletRequest request) { > try { > Thread.sleep(5000); > } catch (InterruptedException e) { > e.printStackTrace(); > } > System.out.println(request); > return request.toString(); > } > } **优缺点** > **缺点:冗余** > > 1. 如果多个controller方法中都需要request对象,那么在每个方法中都需要添加一遍request参数 > 2. request对象的获取只能从controller开始,若使用request对象的地方在函数调用层级比较深的地方,那么整个调用链上的所有方法都要添加request参数 > > 实际上,在整个请求处理的过程中,request对象是贯穿始终的;也就是说,除了定时器等特殊情况,request对象相当于线程内部的一个全局变量。而该方法,相当于将这个全局变量,传来传去。 **线程安全性** > **测试结果:线程安全** > > 分析:此时request对象是方法参数,相当于局部变量,毫无疑问是线程安全的。 > **测试:接口中睡眠,模拟多个请求同时处理** > > 直接用“**代码示例**”中的代码 > > Postman开两个窗口,都访问:[http://localhost:8080/test1][http_localhost_8080_test1] > > 结果:(两个不同的request,可见线程安全的) > > org.apache.catalina.connector.RequestFacade@4613eaaa > org.apache.catalina.connector.RequestFacade@492b9e2e > > 注意:如果不加睡眠,结果可能是:两个相同的request,因为如果它能处理过来,就会用同一个request去接收了。 ### **法2:自动注入** ### **代码示例** > package com.example.controller; > > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.web.bind.annotation.GetMapping; > import org.springframework.web.bind.annotation.RequestMapping; > import org.springframework.web.bind.annotation.RestController; > > import javax.servlet.http.HttpServletRequest; > > @RestController > @RequestMapping > public class HelloController { > @Autowired > private HttpServletRequest request; > > @GetMapping("/test1") > public String test1() { > try { > Thread.sleep(5000); > } catch (InterruptedException e) { > e.printStackTrace(); > } > System.out.println(request); > return request.toString(); > } > } **线程安全性** > **线程安全(不是测出来的,是看代码得出的)** > > 分析:在Spring中,Controller的scope是singleton(单例),也就是说在整个web系统中,只有一个TestController;但是其注入的request却是线程安全的。 > **测试:接口中睡眠,模拟多个请求同时处理** > > 使用上边“**代码示例**”中的代码 > > Postman开两个窗口,都访问:[http://localhost:8080/test1][http_localhost_8080_test1] > > 结果:(这个结果很奇怪,只能去追寻源码。见:[SpringMVC原理--Controller线程安全\_feiying0canglang的博客-CSDN博客][SpringMVC_--Controller_feiying0canglang_-CSDN]) > > Current HttpServletRequest > Current HttpServletRequest **优缺点** > **优点** > > 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相比大大减少了代码冗余。 > > **缺点** > > 1. 仍然存在代码冗余。场景如下: > > 1. web中有很多controller,每个controller中都会使用request对象(这种场景实际上非常频繁),这时就需要写很多次注入request的代码;如果还需要注入response,代码就更繁琐了。下面说明自动注入方法的改进方法,并分析其线程安全性及优缺点。 ### **法3:基类中自动注入** ### **说明** > 与方法2相比,将注入部分代码放入到了基类中。 **代码示例** > **基类代码**: > > public class BaseController { > @Autowired > protected HttpServletRequest request; > } > > **Controller代码** > > 这里列举了BaseController的两个派生类,由于此时测试代码会有所不同,因此服务端测试代码没有省略;客户端也需要进行相应的修改(同时向2个url发送大量并发请求)。 > > @Controller > public class TestController extends BaseController { > // 存储已有参数,用于判断参数value是否重复,从而判断线程是否安全 > public static Set<String> set = new ConcurrentSkipListSet<>(); > @RequestMapping("/test") > public void test() throws InterruptedException { > String value = request.getParameter("key"); > // 判断线程安全 > if (set.contains(value)) { > System.out.println(value + "\t重复出现,request并发不安全!"); > } else { > System.out.println(value); > set.add(value); > } > // 模拟程序执行了一段时间 > Thread.sleep(1000); > } > } > @Controller > public class Test2Controller extends BaseController { > @RequestMapping("/test2") > public void test2() throws InterruptedException { > String value = request.getParameter("key"); > // 判断线程安全(与TestController使用一个set进行判断) > if (TestController.set.contains(value)) { > System.out.println(value + "\t重复出现,request并发不安全!"); > } else { > System.out.println(value); > TestController.set.add(value); > } > // 模拟程序执行了一段时间 > Thread.sleep(1000); > } > } **线程安全性** > 测试结果:线程安全 > > 分析:在理解了方法2的线程安全性的基础上,很容易理解方法3是线程安全的:当创建不同的派生类对象时,基类中的域(这里是注入的request)在不同的派生类对象中会占据不同的内存空间,也就是说将注入request的代码放在基类中对线程安全性没有任何影响;测试结果也证明了这一点。 **优缺点** > 与方法2相比,避免了在不同的Controller中重复注入request;但是考虑到java只允许继承一个基类,所以如果Controller需要继承其他类时,该方法便不再好用。 > > 无论是方法2和方法3,都只能在Bean中注入request;如果其他方法(如工具类中static方法)需要使用request对象,则需要在调用这些方法时将request参数传递进去。下面介绍的方法4,则可以直接在诸如工具类中的static方法中使用request对象(当然在各种Bean中也可以使用)。 ### **法4:@ModelAttribute(错误方法)** ### **代码示例** > 下面这种方法及其变种(变种:将request和bindRequest放在子类中)在网上经常见到: > > package com.example.controller; > > import org.springframework.web.bind.annotation.GetMapping; > import org.springframework.web.bind.annotation.ModelAttribute; > import org.springframework.web.bind.annotation.RequestMapping; > import org.springframework.web.bind.annotation.RestController; > > import javax.servlet.http.HttpServletRequest; > > @RestController > @RequestMapping > public class HelloController { > protected HttpServletRequest request; > > @ModelAttribute > public void bindreq(HttpServletRequest request) { > this.request = request; > } > > > @GetMapping("/test1") > public String test1() { > try { > Thread.sleep(5000); > } catch (InterruptedException e) { > e.printStackTrace(); > } > System.out.println(request); > return request.toString(); > } > } **线程安全性** > **测试结果:线程不安全** > > 分析:@ModelAttribute注解用在Controller中修饰方法时,其作用是Controller中的每个@RequestMapping方法执行前,该方法都会执行。因此在本例中,bindRequest()的作用是在test()执行前为request对象赋值。 > > 虽然bindRequest()中的参数request本身是线程安全的,但由于TestController是单例的,request作为TestController的一个域,无法保证线程安全。 > **测试:接口中睡眠,模拟多个请求同时处理** > > 使用上边“**代码示例**”中的代码 > > Postman开两个窗口,都访问:[http://localhost:8080/test1][http_localhost_8080_test1] > > 结果:(是同一个request,可见线程不安全) > > org.apache.catalina.connector.RequestFacade@3bb8d854 > org.apache.catalina.connector.RequestFacade@3bb8d854 ## **方案3:手动调用** ## > **代码示例** > > @Controller > public class TestController { > @RequestMapping("/test") > public void test() throws InterruptedException { > HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); > // 模拟程序执行了一段时间 > Thread.sleep(1000); > } > } **线程安全性** > 测试结果:线程安全 > > 分析:该方法与方法2(自动注入)类似,只不过方法2中通过自动注入实现,本方法通过手动方法调用实现。因此本方法也是线程安全的。 **优缺点** > **优点** > > 1. 可以在非Bean中直接获取。 > > **缺点** > > 1. 如果使用的地方较多,代码非常繁琐;因此可以与其他方法配合使用。 # HttpServletRequest方法 # ## 获取请求信息 ## > <table> > <tbody> > <tr> > <td style="vertical-align:top;width:91px;"> <p style="margin-left:0pt;"><strong>返回值类型</strong></p> </td> > <td style="vertical-align:top;width:173px;"> <p style="margin-left:0pt;"><strong><strong>方法声明</strong></strong></p> </td> > <td style="vertical-align:top;"> <p style="margin-left:0pt;"><strong><strong>功能描述</strong></strong></p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getMethod()</p> </td> > <td> <p style="margin-left:0pt;">获取HTTP请求消息中的请求方式(如GET、POST等)</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getRequestURI()</p> </td> > <td> <p style="margin-left:0pt;">获取请求行中资源名称部分,即位于URL的主机和端口之后、参数部分之前的部分</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getQueryString()</p> </td> > <td> <p style="margin-left:0pt;">获取请求行中的参数部分,也就是资源路径后面问号(?)以后的所有内容</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getProtocol()</p> </td> > <td> <p style="margin-left:0pt;">获取请求行中的协议名和版本,例如,HTTP/1.0或HTTP/1.1</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getContextPath()</p> </td> > <td> <p style="margin-left:0pt;">获取请求URL中属于WEB应用程序的路径,这个路径以“/”开头,表示相对于整个WEB站点的根目录,路径结尾不含“/”。</p> <p style="margin-left:0pt;">如果请求URL属于WEB站点的根目录,那么返回结果为空字符串("")</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getServletPath()</p> </td> > <td> <p style="margin-left:0pt;">获取Servlet所映射的路径(url-pattern)</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getRemoteAddr()</p> </td> > <td> <p style="margin-left:0pt;">获取请求客户端的IP地址,其格式类似于“192.168.0.3”</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getRemoteHost()</p> </td> > <td> <p style="margin-left:0pt;">获取请求客户端的完整主机名,其格式类似于“www.sw.com”。</p> <p style="margin-left:0pt;">需要注意的是,如果无法解析出客户机的完整主机名,该方法将会返回客户端的IP地址</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getRemotePort()</p> </td> > <td> <p style="margin-left:0pt;">获取请求客户端网络连接的端口号</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getLocalAddr()</p> </td> > <td> <p style="margin-left:0pt;">获取Web服务器上接收当前请求网络连接的IP地址</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getLocalName()</p> </td> > <td> <p style="margin-left:0pt;">获取Web服务器上接收当前网络连接IP所对应的主机名</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">int</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getLocalPort()</p> </td> > <td> <p style="margin-left:0pt;">获取Web服务器上接收当前网络连接的端口号</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getServerName()</p> </td> > <td> <p style="margin-left:0pt;">获取当前请求所指向的主机名,即HTTP请求消息中Host头字段所对应的主机名部分</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">int</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getServerPort()</p> </td> > <td> <p style="margin-left:0pt;">获取当前请求所连接的服务器端口号,即如果HTTP请求消息中Host头字段所对应的端口号部分</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getScheme()</p> </td> > <td> <p style="margin-left:0pt;">获取请求的协议名,例如http、https或ftp</p> </td> > </tr> > <tr> > <td style="width:91px;"> <p style="margin-left:0pt;">StringBuffer</p> </td> > <td style="width:173px;"> <p style="margin-left:0pt;">getRequestURL()</p> </td> > <td> <p style="margin-left:0pt;">获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等信息,但不包括后面的查询参数部分。</p> <p style="margin-left:0pt;">注意,getRequestURL()方法返回的结果是StringBuffer类型,而不是String类型,这样更便于对结果进行修改</p> </td> > </tr> > </tbody> > </table> ## 获得请求头 ## > <table> > <tbody> > <tr> > <td style="vertical-align:top;width:124px;"> <p style="margin-left:0pt;"><strong>返回值类型</strong></p> </td> > <td style="vertical-align:top;width:218px;"> <p style="margin-left:0pt;"><strong><strong>方法声明</strong></strong></p> </td> > <td style="vertical-align:top;"> <p style="margin-left:0pt;"><strong><strong>功能描述</strong></strong></p> </td> > </tr> > <tr> > <td style="width:124px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:218px;"> <p style="margin-left:0pt;">getHeader(String name)</p> </td> > <td> <p style="margin-left:0pt;">获取一个指定头字段的值,如果请求消息中没有包含指定的头字段,getHeader()方法返回null;</p> <p style="margin-left:0pt;">如果请求消息中包含有多个指定名称的头字段,getHeader()方法返回其中第一个头字段的值</p> </td> > </tr> > <tr> > <td style="width:124px;"> <p style="margin-left:0pt;">Enumeration</p> </td> > <td style="width:218px;"> <p style="margin-left:0pt;">getHeaders(String name)</p> </td> > <td> <p style="margin-left:0pt;">该方法返回一个Enumeration集合对象,该集合对象由请求消息中出现的某个指定名称的所有头字段值组成。</p> <p style="margin-left:0pt;">在多数情况下,一个头字段名在请求消息中只出现一次,但有时候可能会出现多次</p> </td> > </tr> > <tr> > <td style="width:124px;"> <p style="margin-left:0pt;">Enumeration</p> </td> > <td style="width:218px;"> <p style="margin-left:0pt;">getHeaderNames()</p> </td> > <td> <p style="margin-left:0pt;">获取一个包含所有请求头字段的Enumeration对象</p> </td> > </tr> > <tr> > <td style="width:124px;"> <p style="margin-left:0pt;">int</p> </td> > <td style="width:218px;"> <p style="margin-left:0pt;">getIntHeader(String name)</p> </td> > <td> <p style="margin-left:0pt;">获取指定名称的头字段,并且将其值转为int类型。需要注意的是,如果指定名称的头字段不存在,返回值为-1;</p> <p style="margin-left:0pt;">如果获取到的头字段的值不能转为int类型,将发生NumberFormatException异常</p> </td> > </tr> > <tr> > <td style="width:124px;"> <p style="margin-left:0pt;">Long</p> </td> > <td style="width:218px;"> <p style="margin-left:0pt;">getDateHeader(String name)</p> </td> > <td> <p style="margin-left:0pt;">获取指定头字段的值,并将其按GMT时间格式转换成一个代表日期/时间的长整数,</p> <p style="margin-left:0pt;">这个长整数是自1970年1月1日0点0分0秒算起的以毫秒为单位的时间值</p> </td> > </tr> > </tbody> > </table> ## 获取请求参数 ## > <table> > <tbody> > <tr> > <td style="vertical-align:top;width:107px;"> <p style="margin-left:0pt;"><strong><strong>返回值类型</strong></strong></p> </td> > <td style="vertical-align:top;width:241px;"> <p style="margin-left:0pt;"><strong><strong>方法声明</strong></strong></p> </td> > <td style="vertical-align:top;"> <p style="margin-left:0pt;"><strong><strong>功能描述</strong></strong></p> </td> > </tr> > <tr> > <td style="width:107px;"> <p style="margin-left:0pt;">String</p> </td> > <td style="width:241px;"> <p style="margin-left:0pt;">getParameter(String name)</p> </td> > <td> <p style="margin-left:0pt;">获取某个指定名称的参数值,如果请求消息中没有包含指定名称的参数,getParameter()方法返回null;</p> <p style="margin-left:0pt;">如果指定名称的参数存在但没有设置值,则返回一个空串;</p> <p style="margin-left:0pt;">如果请求消息中包含有多个该指定名称的参数,getParameter()方法返回第一个出现的参数值</p> </td> > </tr> > <tr> > <td style="width:107px;"> <p style="margin-left:0pt;">String[]</p> </td> > <td style="width:241px;"> <p style="margin-left:0pt;">getParameterValues(String name)</p> </td> > <td> <p style="margin-left:0pt;">HTTP请求消息中可以有多个相同名称的参数(通常由一个包含有多个同名的字段元素的FORM表单生成),</p> <p style="margin-left:0pt;">如果要获得HTTP请求消息中的同一个参数名所对应的所有参数值,那么就应该使用getParameterValues()方法,</p> <p style="margin-left:0pt;">返回一个String类型的数组</p> </td> > </tr> > <tr> > <td style="width:107px;"> <p style="margin-left:0pt;">Enumeration</p> </td> > <td style="width:241px;"> <p style="margin-left:0pt;">getParameterNames()</p> </td> > <td> <p style="margin-left:0pt;">getParameterNames()方法用于返回一个包含请求消息中所有参数名的Enumeration对象,</p> <p style="margin-left:0pt;">在此基础上,可以对请求消息中的所有参数进行遍历处理</p> </td> > </tr> > <tr> > <td style="width:107px;"> <p style="margin-left:0pt;">Map</p> </td> > <td style="width:241px;"> <p style="margin-left:0pt;">getParameterMap()</p> </td> > <td> <p style="margin-left:0pt;">getParameterMap()方法用于将请求消息中的所有参数名和值装入进一个Map对象中返回</p> </td> > </tr> > </tbody> > </table> [SpringMVC--HttpServletRequest_IT_-CSDN]: https://knife.blog.csdn.net/article/details/108372719 [servlet]: http://www.51gjie.com/javaweb/846.html [SpringMVC_Controller_request_ - sluggarddd_ - OSCHINA -]: https://my.oschina.net/sluggarddd/blog/678603?fromerr=XhvpvVTi [Spring_request_ - _ -]: https://www.cnblogs.com/kismetv/p/8757260.html#t7 [SpringBoot_Request_Response_jiulanhao_-CSDN_springboot_request]: https://blog.csdn.net/jiulanhao/article/details/83068952 [SpringMVC_--_feiying0canglang_-CSDN]: https://blog.csdn.net/feiying0canglang/article/details/108275174 [SpringMVC_--_feiying0canglang_-CSDN 1]: https://blog.csdn.net/feiying0canglang/article/details/108372756 [http_localhost_8080_test1]: http://localhost:8080/test1 [SpringMVC_--Controller_feiying0canglang_-CSDN]: https://blog.csdn.net/feiying0canglang/article/details/115423111
还没有评论,来说两句吧...