- HttpServletRequest接口详解
2.5 ★★★HttpServletRequest接口详解
HttpServletRequest
:HttpServletRequest
是什么?jakarta.servlet.http.HttpServletRequest
,是 Servlet 规范中的一员。HttpServletRequest
是一个接口,全限定名称为jakarta.servlet.http.HttpServletRequest
。HttpServletRequest
接口的父接口是ServletRequest
:1
public interface HttpServletRequest extends ServletRequest {}
HttpServletRequest
接口的实现类谁写的?HttpServletRequest
对象是谁给创建的?通过测试:
org.apache.catalina.connector.RequestFacade
实现了HttpServletRequest
接口:1
public class RequestFacade implements HttpServletRequest {}
测试结果说明:Tomcat 服务器(WEB服务器、WEB容器)实现了
HttpServletRequest
接口,还说明了 Tomcat 服务器实现了 Servlet 规范。而对于我们 javaweb 程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可,我们关心的是
HttpServletRequest
接口中有哪些方法,这些方法可以完成什么功能。
HttpServletRequest
对象中都有什么信息?都包装了什么信息?HttpServletRequest
对象是 Tomcat 服务器负责创建的,这个对象中封装了 HTTP 的请求协议。- 实际上是用户发送请求的时候,遵循了 HTTP 协议,发送的是 HTTP 的请求协议,Tomcat 服务器将 HTTP 协议中的信息以及数据全部解析出来,然后 Tomcat 服务器把这些信息封装到
HttpServletRequest
对象当中,传给了我们 javaweb 程序员。 - javaweb 程序员面向
HttpServletRequest
接口编程,调用方法就可以获取到请求的信息了。
request
和response
对象的生命周期?request
对象和response
对象,一个是请求对象,一个是响应对象,这两个对象只在当前请求中有效。- 一次请求对应一个
request
。 - 两次请求则对应两个
request
。 - …
★
HttpServletRequest
怎么获取前端浏览器用户提交的数据?思考:如果是你,前端的 form 表单提交了数据之后,你准备怎么存储这些数据,你准备采用什么样的数据结构去存储这些数据呢?
前端提交的数据格式:
username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt
可以采用
Map
集合来存储:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Map<String,String>
key存储String
value存储String
这种想法对吗?不对。
如果采用以上的数据结构存储会发现key重复的时候value覆盖。
key value
---------------------
username abc
userpwd 111
aihao s
aihao d
aihao tt
这样是不行的,因为map的key不能重复。
Map<String, String[]>
key存储String
value存储String[]
key value
-------------------------------
username {"abc"}
userpwd {"111"}
aihao {"s","d","tt"}注意:前端表单提交数据的时候,假设提交了 120 这样的“数字”,其实是以字符串 “120” 的方式提交的,所以服务器端获取到的一定是一个字符串的”120”,而不是一个数字(前端提交的永远是字符串,后端获取的也永远是字符串)。
因此,下面的四个方法就是获得上面的
Map
集合中的数据:1
2
3
4Map<String,String[]> getParameterMap() 获取Map集合
Enumeration<String> getParameterNames() 获取Map集合中所有的key
String[] getParameterValues(String name) 根据key获取Map集合的value
String getParameter(String name) 根据key获取value这个一维数组当中的**第一个元素**。这个方法最常用。
request
对象又称为 “请求域” 对象。“请求域”对象:
“请求域” 对象要比 “应用域” 对象范围小很多,生命周期短很多,请求域只在一次请求内有效。
一个请求对象
request
对应一个请求域对象,一次请求结束之后,这个请求域就销毁了。请求域对象也有这三个方法:
1
2
3void setAttribute(String name, Object obj); // 向域当中绑定数据。
Object getAttribute(String name); // 从域当中根据name获取数据。
void removeAttribute(String name); // 将域当中绑定的数据移除1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package com.f.javaweb.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
/**
* @author fzy
* @date 2023/11/28 19:26
*/
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 向request域中绑定数据
Date time = new Date();
request.setAttribute("sysTime", time);
// 从request域中获取数据
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Date sysTime = (Date) request.getAttribute("sysTime");
out.print("request域中获取的系统的当前时间: " + sysTime + "<br/>");
// 移除request域中的数据
request.removeAttribute("sysTime");
}
}
“请求域” 和 “应用域” 的选用原则?
- 尽量使用小的域对象,因为小的域对象占用的资源较少。
★跳转:
转发(一次请求):在执行了
AServlet
之后,跳转到BServlet
,两个Servlet
共享同一个request
和response
对象。AServlet
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package com.f.javaweb.servlet;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
/**
* @author fzy
* @date 2023/11/28 19:26
*/
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 向request域中绑定数据
Date time = new Date();
request.setAttribute("sysTime", time);
// 使用Servlet当中的转发机制
// 执行了AServlet之后,跳转到BServlet(这个资源跳转可以使用转发机制来完成)
// 思考:这样做可以吗?
// 在AServlet当中new一个BServlet对象,然后调用BServlet对象的doGet方法,把request对象传过去。
/* BServlet bServlet = new BServlet();
bServlet.doGet(request, response); */
// 这个代码虽然可以实现功能,但是Servlet对象不能自己由程序员来new。
// 自己new的Servlet对象生命周期不受Tomcat服务器的管理。
// 怎么转发?代码怎么写?
// 第一步:获取请求转发器对象
// 把"/b"这个路径包装到请求转发器当中,相当于把下一个跳转的资源的路径告知给Tomcat服务器了
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/b");
// 第二步:调用请求转发器RequestDispatcher的forward方法。进行转发。
// 转发的时候:这两个参数很重要。request和response都是要传递给下一个资源的
requestDispatcher.forward(request, response);
//// 一行代码搞定转发。
//request.getRequestDispatcher("/b").forward(request, response);
}
}BServlet
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.f.javaweb.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
/**
* @author fzy
* @date 2023/11/28 19:38
*/
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 从request域中获取数据
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Date sysTime = (Date) request.getAttribute("sysTime");
out.print("BSerlvet从request域中获取的系统的当前时间: " + sysTime + "<br/>");
}
}跳转的核心代码:
1
2
3
4
5
6
7// 第一步:获取请求转发器对象
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/b");
// 第二步:调用请求转发器RequestDispatcher的forward方法。进行转发。
requestDispatcher.forward(request, response);
// 也可以一行代码搞定转发。
request.getRequestDispatcher("/b").forward(request, response);
HttpServletRequest
接口的其他常用方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();
// request.getRemoteHost(); 获取远程主机地址
// request.getRemotePort(); 获取端口
// get请求在请求行上提交数据
// post请求在请求体中提交数据
// 设置请求体的字符集(显然这个方法是处理POST请求的乱码问题。这种方式并不能解决GET请求的乱码问题)
// Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。
// Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?在获取请求体数据之前执行以下代码。
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name");
// 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码?
response.setContentType("text/html;charset=UTF-8");
// 在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了。以上代码就不需要设置UTF-8了。
// 注意一个细节
// 在Tomcat10包括10在内之后的版本,中文将不再出现乱码(这也体现了中文地位的提升)
// get请求乱码问题怎么解决?
// get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的。
// get请求乱码怎么解决
// 方案:修改CATALINA_HOME/conf/server.xml配置文件
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
URIEncoding="UTF-8" //加入的部分
/>
// 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了
// 获取应用的根路径
String contextPath = request.getContextPath(); // /aaa
// 获取请求方式
String method = request.getMethod();
// 获取请求的URI
String uri = request.getRequestURI(); // /aaa/testRequest
// 获取servlet path,不带项目名
String servletPath = request.getServletPath(); // /testRequest
思考
两个
Servlet
对象怎么共享数据?- 将数据放到
ServletContext
应用域当中,当然是可以的,但是应用域范围太大,占用资源太多,不建议使用。 - 可以将数据放到
request
域当中,然后AServlet
转发到BServlet
,保证AServlet
和BServlet
在同一次请求当中,这样就可以做到两个Servlet
,或者多个Servlet
共享同一份数据。
- 将数据放到
转发的下一个资源必须是一个
Servlet
吗?不一定,只要是 Tomcat 服务器当中的合法资源,都是可以转发的。例如:html…
1
2// 可以转发到一个Servlet,也可以转发到一个HTML,只要是WEB容器当中的合法资源即可。
request.getRequestDispatcher("/test.html").forward(request, response);注意:转发的时候,路径的写法要注意,转发的路径以 “/” 开始,不加项目名。
注意
关于
request
对象中两个非常容易混淆的方法:1
2
3
4
5
6
7
8
9// uri?username=zhangsan&userpwd=123&sex=1
String username = request.getParameter("username");
// 之前一定是执行过:request.setAttribute("name", new Object())
Object obj = request.getAttribute("name");
// 以上两个方法的区别是什么?
// **第一个方法:获取的是用户在浏览器上提交的数据。**
// **第二个方法:获取的是请求域当中绑定的数据。**
2.6 ★★★转发和重定向
在一个 web 应用中可以通过两种方式,来完成资源的跳转:
- 第一种方式:转发
- 第二种方式:重定向
转发和重定向有什么区别?
代码上有什么区别?
转发:
1
2
3
4
5
6
7
8
9
10
11
12// 获取请求转发器对象
// 告诉Tomcat下一次请求要发给哪个Servlet对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list");
// 调用请求转发器对象的forward方法完成转发
// 传入request, response 保证为同一次请求,保证请求域相同
dispatcher.forward(request, response);
// 合并一行代码
request.getRequestDispatcher("/dept/list").forward(request, response);
// 转发的时候是一次请求,不管你转发了多少次。都是一次请求。
// AServlet转发到BServlet,再转发到CServlet,再转发到DServlet,不管转发了多少次,都在同一个request当中。
// 这是因为调用forward方法的时候,会将当前的request和response对象传递给下一个Servlet。重定向:
1
2
3
4
5// 注意:路径上要加一个项目名。为什么?
// 浏览器发送请求,请求路径上是需要添加项目名的。
// 以下这一行代码会将请求路径“/oa/dept/list”发送给浏览器
// 浏览器会自发的向服务器发送一次全新的请求:/oa/dept/list
response.sendRedirect("/oa/dept/list");
注意:
- 转发是通过
request
对象来得到RequestDispatcher
对象,再进行转发;重定向是通过response
对象来进行重定向。 - 转发的路径不需要带项目名,重定向的路径要加上项目名。因为转发是服务器来控制的(可以看作是后端),重定向是浏览器来执行的(可以看作是前端)。
形式上有什么区别?
转发(一次请求):
在浏览器地址栏上发送的请求是:http://localhost:8080/servlet09/a ,最终请求结束之后,浏览器地址栏上的地址还是这个,没变。

重定向(一次重定向总共会有两次请求、两次重定向总共会有三次请求…):
在浏览器地址栏上发送的请求是:http://localhost:8080/servlet09/a ,最终在浏览器地址栏上显示的地址是:http://localhost:8080/servlet09/b 。

转发和重定向的本质区别:
转发:是由 WEB 服务器来控制的,A资源跳转到B资源,这个跳转动作是Tomcat服务器内部完成的,这些跳转的资源之间共享同一个
request
和response
。重定向:是浏览器完成的。具体跳转到哪个资源,是浏览器说了算。
使用一个例子去描述这个转发和重定向:
- 借钱(转发:发送了一次请求)
- 杜老师没钱了,找张三借钱,其实张三没有钱,但是张三够义气,张三自己找李四借了钱,然后张三把这个钱给了杜老师,杜老师不知道这个钱是李四的,杜老师只求了一个人。杜老师以为这个钱就是张三的。
- 借钱(重定向:发送了两次请求)
- 杜老师没钱了,找张三借钱,张三没有钱,张三有一个好哥们,叫李四,李四是个富二代,于是张三将李四的家庭住址告诉了杜老师,杜老师按照这个地址去找到李四,然后从李四那里借了钱。显然杜老师在这个过程中,求了两个人。并且杜老师知道最终这个钱是李四借给他的。
- 借钱(转发:发送了一次请求)
转发和重定向应该如何选择?什么时候使用转发,什么时候使用重定向?
- 如果在上一个
Servlet
当中向request
域当中绑定了数据,希望从下一个Servlet
当中把request
域里面的数据取出来,则使用转发机制。 - 剩下所有的请求均使用重定向(重定向使用较多)。
- 如果在上一个
思考
跳转的下一个资源有没有要求呢?必须是一个
Servlet
吗?- 不一定,跳转的资源只要是服务器内部合法的资源即可,包括:Servlet、JSP、HTML…
转发会存在浏览器的刷新问题,由于请求转发的前端路径不会发生变化,所以前端进行刷新时会重新执行一次与之前相同的请求,如果之前执行的请求为向数据库插入信息,则使用转发每次进行刷新都会重新执行一次插入操作。
使用重定向则不会,重定向是发送一次与之前请求无关的一次新请求。